删除触发后的Oracle ...如何避免变异表(ORA-04091)?

时间:2011-04-27 14:46:28

标签: oracle triggers ora-04091

假设我们有以下表结构:

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。

2 个答案:

答案 0 :(得分:9)

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

  • 包含密钥集合的包(在本例中为docId)。临时表也可以使用
  • 初始化集合的before语句触发器
  • 使用已更改的每个docId填充集合的行级触发器
  • 一个after语句触发器,它遍历集合并执行实际的UPDATE

类似

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;

答案 1 :(得分:1)

似乎是this question

的副本

结帐Tom Kyte's take on that