0

我想在表(CMS_CONTENT_ENROLLMENT)上插入或更新,然后对同一个表进行计数并将结果更新到另一个表中。可悲的是,我收到了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;
/
4

2 回答 2

0

警告语:我认为在触发器中嵌入复杂的应用程序逻辑代码不是一个好主意,因为代码分布在许多对象之间,并且很难测试和维护。通常 ORA-04091 表明您正在尝试执行一些对于触发器来说过于复杂的事情。这可以被规避,但会导致复杂的逻辑在某些方面经常存在缺陷,尤其是在像数据库这样的多用户环境中。出于这个原因,如果您想要一个汇总表,我建议您使用常规视图、物化视图或程序逻辑。

如果您无论如何都想使用触发器,则可以使其与增量逻辑一起使用,如下所示(未经测试,假设您从未更新contendid,即deletedNOT 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;
/
于 2013-02-13T14:44:01.277 回答
0

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

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

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

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

于 2013-02-14T01:17:51.770 回答