ORTER-04091 AFTER INSERT触发期间的变异表错误

时间:2017-07-26 21:11:55

标签: oracle plsql oracle11g database-trigger

我在名为DM_USER_ROLE

的表上有以下AFTER INSERT触发器
create or replace TRIGGER  "DM_USER_ROLE_T1" 
AFTER
insert on "DM_USER_ROLE"
for each row
DECLARE 
  v_cert_enrolment_id number;
  v_user_role_id number;
begin

v_cert_enrolment_id := "DM_CERTIFICATION_ENROLMEN_SEQ".nextval;
v_user_role_id := :new.USER_ROLE_ID;

/*
  When a user is assigned a role, we create an enrolment record
  in DM Certification record linked to this user/role combination.
  We also insert into the DM_COURSE_ENROLMENT table the courses
  associated with the certfication
*/

--FIRST AN ENROLMENT RECORD IS CREATED IN DM_CERTIFICATION_ENROLMENT
INSERT INTO DM_CERTIFICATION_ENROLMENT
(CERTIFICATION_ENROLMENT_ID, ALLOCATED_DT, DEADLINE_DATE, STATUS, USER_ROLE_ID)
VALUES
(
  v_cert_enrolment_id,
  trunc(sysdate), 
  trunc(sysdate) + 60,
  'Enrolled',
  v_user_role_id
  );

  --COURSES LINKED TO THE CERTIFICATION ARE INSERTED INTO DM_COURSE_ENROLMENT
INSERT INTO DM_COURSE_ENROLMENT
  (
    CERTIFICATION_ENROLMENT_ID,
    COURSE_ID,
    ALLOCATED_DT,
    DEADLINE_DT,
    STATUS
    )
SELECT v_cert_enrolment_id,
       COURSE.COURSE_ID,
       trunc(sysdate),
       trunc(sysdate) + 60,
       'Enrolled'
FROM DM_CERTIFICATION_COURSE COURSE
WHERE CERTIFICATION_ID = 
(
  SELECT  C.CERTIFICATION_ID FROM 
    DM_CERTIFICATION_ENROLMENT A, 
    DM_USER_ROLE B,
    DM_ROLE_CERTIFICATION C
  WHERE 
    A.USER_ROLE_ID = B.USER_ROLE_ID 
    AND 
    B.ROLE_ID = C.ROLE_ID
    AND 
    A.CERTIFICATION_ENROLMENT_ID = v_cert_enrolment_id
);

EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
        DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 2000));




end;

当在此表中发生插入时,我需要填充2个单独的表,我认为AFTER INSERT触发器避免了变异表的问题?

我不确定是什么导致它,也许是DM_USER_ROLE的第二个INSERT语句中的读取,这是启动此触发器的地方......但我的印象是AFTER INSERTs可以安全地避免突变,因为更新已经发生了。

错误是:

  

ORA-04091:表AZLEARN_BACKUP.DM_USER_ROLE正在变异,   触发器/功能可能看不到它

第一次插入发生,第二次插入不发生。

这篇文章让我相信AFTER触发器是安全的。

http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm

------- UPDATE ---------------

我更改了它,使用两个参数化游标逐行插入并且它有效...仍然不确定错误是什么:

create or replace TRIGGER  "DM_USER_ROLE_T1" 
AFTER
insert on "DM_USER_ROLE"
for each row
DECLARE 
  v_cert_enrolment_id number;
  v_user_role_id number;
  v_role_id number;
  v_certification_id number;

 cursor certs_for_role(p_role_id number) is
  select * from DM_ROLE_CERTIFICATION where ROLE_ID = p_role_id;

 r_certs_for_role certs_for_role%rowtype;

  cursor courses_for_certs(p_cert_id number) is
  select * from DM_CERTIFICATION_COURSE where CERTIFICATION_ID = p_cert_id;

  r_courses_for_certs courses_for_certs%rowtype;

begin


v_user_role_id := :new.USER_ROLE_ID;
v_role_id := :new.ROLE_ID;


open certs_for_role(v_role_id);
loop
  fetch certs_for_role into r_certs_for_role;  
    exit when certs_for_role%notfound;
      v_cert_enrolment_id := "DM_CERTIFICATION_ENROLMEN_SEQ".nextval;

      INSERT INTO DM_CERTIFICATION_ENROLMENT
      (CERTIFICATION_ENROLMENT_ID, ALLOCATED_DT, DEADLINE_DATE, STATUS, USER_ROLE_ID, CERTIFICATION_ID)
      VALUES
      (
        v_cert_enrolment_id,
        trunc(sysdate), 
        trunc(sysdate) + 60,
        'Enrolled',
        v_user_role_id,
        r_certs_for_role.CERTIFICATION_ID
      );

    open courses_for_certs(r_certs_for_role.CERTIFICATION_ID);
      loop
        fetch courses_for_certs into r_courses_for_certs;
        exit when courses_for_certs%notfound;
        INSERT INTO DM_COURSE_ENROLMENT
        (
          CERTIFICATION_ENROLMENT_ID,
          COURSE_ID,
          ALLOCATED_DT,
          DEADLINE_DT,
          STATUS
        )
        VALUES
        (
          v_cert_enrolment_id,
          r_courses_for_certs.COURSE_ID,
          trunc(sysdate),
          trunc(sysdate) + 60,
          'Enrolled'        
        );    
      end loop;
    close courses_for_certs;
end loop;

close certs_for_role;


EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
        DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 2000));




end;

2 个答案:

答案 0 :(得分:1)

原因很简单,您无法从ROW LEVEL触发器所基于的表DM_USER_ROLE中进行选择。

在你的第一个解决方案中,你有一个

SELECT ...
FROM DM_USER_ROLE ...

这是不允许的。您的第二个触发器不会选择表DM_USER_ROLE,因此它正在运行。

链接页面中的建议是正确的但是当他们在“或”而不是“触发器”之后声明“使用”时会产生误导 - 应该更准确地说“使用”后面的语句“或”而不是“触发器”。 Oracle根据以下操作提供触发器:

  • 特定表或视图上的DML语句(INSERT,UPDATE,DELETE)
  • DDL语句(主要是CREATE或ALTER)
  • 数据库事件,例如登录/注销,错误或启动/关闭

你有一个可以有不同时间点的DML触发器:

  • 在触发语句执行之前
  • 执行触发语句后
  • 在触发语句影响的每一行
  • 之前
  • 触发语句影响的每一行
  • 之后
  • 复合触发 - >这个结合了上面列出的四个触发器
  • INSTEAD OF触发器(仅适用于视图)

很多人都错过了语句级触发器和行级触发器之间的区别。

行级触发器具有关键字FOR EACH ROW,并像关键字暗示的那样为每一行运行。如果跳过FOR EACH ROW关键字,则无论您的INSERT / UPDATE / DELETE语句有多少行影响,每个语句只执行一次触发器。

答案 1 :(得分:0)

变异表错误的最可能原因是滥用triggers。这是一个典型的例子:

  

1.您在表A中插入一行

     

2.表A上的触发器(对于每一行)在表A上执行查询,例如计算汇总列

     

3.Oracle抛出一个ORA-04091:表A正在变异,触发器/函数可能看不到它

这是一种预期的正常行为,Oracle希望保护您自己,因为Oracle保证:

  

•(i)每个陈述都是原子的(即要么失败要么成功   完全地)

     

•(ii)每个陈述都能看到一致的数据视图

现在在您的触发器中,当您执行第二次插入时,它正在DM_USER_ROLE表上进行连接以获取记录,这就是您面临的原因

  

ORA-04091:表AZLEARN_BACKUP.DM_USER_ROLE正在变异,   触发器/功能可能看不到它