【问题标题】:Oracle After Delete Trigger... How to avoid Mutating Table (ORA-04091)?删除触发器后的 Oracle...如何避免变异表 (ORA-04091)?
【发布时间】:2011-08-13 22:35:36
【问题描述】:

假设我们有以下表结构:

documents      docmentStatusHistory      status
+---------+   +--------------------+    +----------+
| docId   |   | docStatusHistoryId |    | statusId |
+---------+   +--------------------+    +----------+
| ...     |   | docId              |    | ...      |
+---------+   | statusId           |    +----------+
              | ...                |
              +--------------------+

可能很明显,但值得一提的是,文档的当前状态是最后输入的状态历史记录。

系统的性能缓慢但肯定会下降,我建议将上述结构更改为:

documents           docmentStatusHistory      status
+--------------+   +--------------------+    +----------+
| docId        |   | docStatusHistoryId |    | statusId |
+--------------+   +--------------------+    +----------+
| currStatusId |   | docId              |    | ...      |
| ...          |   | statusId           |    +----------+
+--------------+   | ...                |
                   +--------------------+

这样我们就可以将文档的当前状态放在应有的位置。

由于遗留应用程序的构建方式,我无法更改遗留应用程序上的代码来更新文档表上的当前状态。

在这种情况下,我不得不为我的规则打开一个例外,以不惜一切代价避免触发器,仅仅是因为我无权访问旧版应用程序代码。

我创建了一个触发器,每次将新状态添加到状态历史记录时,它都会更新文档的当前状态,它就像一个魅力。

但是,在不明确且很少使用的情况下,需要删除最后的状态历史记录,而不是简单地添加新的历史记录。所以,我创建了以下触发器:

create or replace trigger trgD_History
 after delete on documentStatusHistory
 for each row
 currentStatusId number;
begin

  select statusId
    into currentStatusId
    from documentStatusHistory
   where docStatusHistoryId = (select max(docStatusHistoryId)
                                 from documentStatusHistory
                                where docId = :old.docId);

  update documentos
     set currStatusId = currentStatusId
   where docId = :old.docId;
end;

这就是我得到臭名昭著的错误ORA-04091的地方。

我了解为什么我收到此错误,即使我将触发器配置为 AFTER 触发器。

问题是我看不到解决此错误的方法。我在网上搜索了一段时间,目前找不到任何有用的东西。

随着时间的推移,我们正在使用 Oracle 9i。

【问题讨论】:

    标签: oracle triggers ora-04091


    【解决方案1】:

    似乎与this question重复

    查看Tom Kyte's take on that

    【讨论】:

    • 不.. 我已经试过了。问题是我需要获取要从中删除的表的最后一个值。
    • Kyte 答案没有解决这种情况。
    • 确实解决了这种情况 IMO。我还打赌贾斯汀的解决方案最初是汤姆的 BTW :)
    【解决方案2】:

    变异表错误的标准解决方法是创建

    • 带有一组键(即本例中的 docId)的包。临时表也可以使用
    • 用于初始化集合的 before 语句触发器
    • 使用每个已更改的 docId 填充集合的行级触发器
    • 循环遍历集合并执行实际更新的 after 语句触发器

    类似

    CREATE OR REPLACE PACKAGE pkg_document_status
    AS
      TYPE typ_changed_docids IS TABLE OF documentos.docId%type;
      changed_docids typ_changed_docids := new typ_changed_docids ();
    
      <<other methods>>
    END;
    
    CREATE OR REPLACE TRIGGER trg_init_collection
      BEFORE DELETE ON documentStatusHistory
    BEGIN
      pkg_document_status.changed_docids.delete();
    END;
    
    CREATE OR REPLACE TRIGGER trg_populate_collection
      BEFORE DELETE ON documentStatusHistory
      FOR EACH ROW
    BEGIN
      pkg_document_status.changed_docids.extend();
      pkg_document_status.changed_docids( pkg_document_status.changed_docids.count() ) := :old.docId;
    END;
    
    CREATE OR REPLACE TRIGGER trg_use_collection
      AFTER DELETE ON documentStatusHistory
    BEGIN
      FOR i IN 1 .. pkg_document_status.changed_docids.count()
      LOOP
        <<fix the current status for pkg_document_status.changed_docids(i) >>
      END LOOP;
      pkg_document_status.changed_docids.delete();
    END;
    

    【讨论】:

    • 我必须进行一些调整才能使其正常工作,但原理是一样的。我必须在包中初始化集合,否则会在trg_init_collection 中报错;从trg_populate_collection 中的.count()+1 中删除+1,因为它给了我SUBSCRIPT_OUTSIDE_LIMIT 错误。
    • 标准的解决方法早于 Oracle 的全局临时表的实现;我记得在 7.3 中使用过该解决方法。
    • 如果我想UPDATE 而不是DELETE 并在表中的特定行上添加锁定,我将如何进行?
    • @RaduGheorghiu - 大概,您的 after delete 语句级触发器将遍历已更新的元素集合并实现您想要的任何业务逻辑(我相信如果您想要使上一行过期'是我想的人)。因此,您将遍历集合,更新与新行重叠的任何行。在这种情况下,我不确定您所说的“在特定行上添加锁”是什么意思。
    • @JustinCave 是的,我认为I am the person you're thinking of。我对你关闭的问题做了一些小的补充,我忘了提到我也有兴趣锁定一些行。
    猜你喜欢
    • 1970-01-01
    • 2012-04-26
    • 1970-01-01
    • 2018-11-30
    • 2011-05-21
    • 1970-01-01
    • 2018-01-02
    • 2012-05-17
    • 2015-08-03
    相关资源
    最近更新 更多