触发 - 选择计数 - ORA-04091

时间:2013-02-13 14:13:21

标签: oracle triggers oracle11g

我想在表(CMS_CONTENT_ENROLLMENT)上INSERT或UPDATE,然后对同一个表进行计数并将结果更新到另一个表中。可悲的是我得到了一个 ORA-04091:表名正在变异,触发器/功能可能看不到它

CREATE OR REPLACE TRIGGER CMS_CONTENT_ENROLLMENT_CNT_TR
    AFTER INSERT OR UPDATE
    ON CMS_CONTENT_ENROLLMENT
    FOR EACH ROW
DECLARE
BEGIN
    UPDATE CMS_CONTENT CNT
    SET CNT.REGISTRATIONCOUNT =
    (
        SELECT COUNT (ENROLLMENTID)
        FROM CMS_CONTENT_ENROLLMENT
        WHERE DELETED = 0 AND CONTENTID = CNT.CONTENTID
    )
    WHERE CNT.CONTENTID = :NEW.CONTENTID;
END;
/

2 个答案:

答案 0 :(得分:0)

警告语:我认为在触发器中嵌入复杂的应用程序逻辑代码并不是一个好主意,因为代码分散在许多对象中,这使得测试和维护变得困难。通常,ORA-04091表示您正在尝试执行过于复杂的触发器操作。这可以避免,但会导致复杂的逻辑在某些方面经常出现问题,特别是在像数据库这样的多用户环境中。出于这个原因,如果你想要一个汇总表,我会建议一个常规视图,一个物化视图或程序逻辑。

如果你想继续使用触发器,你可以使用增量逻辑,就像这样(未经测试,假设你永远不会更新contendiddeleted不是NULL,你永远不会实际上从表中删除并且该行存在于摘要表中):

CREATE OR REPLACE TRIGGER CMS_CONTENT_ENROLLMENT_CNT_TR
   AFTER INSERT OR UPDATE ON CMS_CONTENT_ENROLLMENT
   FOR EACH ROW
DECLARE
BEGIN
   IF :NEW.deleted = 0 AND ((inserting) OR :OLD.deleted != 0) THEN
      UPDATE CMS_CONTENT CNT
         SET CNT.REGISTRATIONCOUNT = CNT.REGISTRATIONCOUNT + 1
       WHERE cnt.contentid = :NEW.contentid;
   ELSIF :NEW.deleted != 0 AND :OLD.deleted = 0 THEN
      UPDATE CMS_CONTENT CNT
         SET CNT.REGISTRATIONCOUNT = CNT.REGISTRATIONCOUNT - 1
       WHERE cnt.contentid = :NEW.contentid;
   END IF;
END;
/

答案 1 :(得分:0)

您的FOR EACH ROW触发器正在查询插入/更新的同一个表。由于触发器触发每一行,查询的结果(如果允许的话)将是不确定的,具体取决于行的处理顺序 - 这是不合逻辑的,因此Oracle引发了ORA-04091: table name is mutating, trigger/function may not see it

此外,我强烈反对Vincent关于在多用户(甚至多会话)环境中编写基于触发器的逻辑的警告。 Oracle将允许您的代码的多个版本同时运行,并且在提交之前他们不会看到彼此的更改。您的触发器已经“计算”了当时可以看到的行,这可能意味着两个会话都会出错。

解决这个问题的唯一方法是(a)构建您的应用程序,这样它就不需要在任何地方存储计数,而是在需要时即时查询它;或(b)引入某种形式的序列化以阻止多个进程同时尝试在表上进行插入/更新。

我强烈推荐选项(a)。除非您首先非常了解Oracle的工作原理,否则选项(b)很容易出错。