【问题标题】:ORA-04091 Mutating Table error during AFTER INSERT TriggerORA-04091 在 AFTER INSERT 触发器期间发生变异表错误
【发布时间】:2018-01-02 09:35:14
【问题描述】:

我在名为 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 语句,这是触发此触发器的位置...但我的印象是,在 INSERT 之后可以安全地避免突变,因为更新已经发生了。

错误是:

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

第一次插入发生,第二次没有。

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

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

-------更新---------------

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

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;

【问题讨论】:

  • 如果可以一次插入一行来避免突变,那么业务逻辑中的明显缺陷可能更容易看到。
  • 有趣的是,第一次插入发生了,第二次没有发生——我原以为这会被视为事务。如何使用光标一次插入一行?
  • 我认为,你应该在一个过程/函数中实现这个逻辑。

标签: oracle plsql oracle11g database-trigger


【解决方案1】:

变异表错误的最可能原因是滥用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 正在变异, 触发器/函数可能看不到它

【讨论】:

    【解决方案2】:

    原因很简单,您无法从 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 语句影响了多少行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-30
      • 2013-05-13
      • 2022-10-20
      • 2012-05-17
      • 1970-01-01
      • 1970-01-01
      • 2015-08-03
      • 2019-09-16
      相关资源
      最近更新 更多