Oracle 删除触发器后...如何避免变异表 (ORA-04091)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5805986/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Oracle After Delete Trigger... How to avoid Mutating Table (ORA-04091)?
提问by Paulo Santos
Let's say we have the following table structures:
假设我们有以下表结构:
documents docmentStatusHistory status
+---------+ +--------------------+ +----------+
| docId | | docStatusHistoryId | | statusId |
+---------+ +--------------------+ +----------+
| ... | | docId | | ... |
+---------+ | statusId | +----------+
| ... |
+--------------------+
It may be obvious, but it's worth mentioning, that the current status of a document is the last Status History entered.
可能很明显,但值得一提的是,文档的当前状态是最后输入的状态历史记录。
The system was slowly but surely degrading in performance and I suggested changing the above structure to:
该系统的性能缓慢但肯定会下降,我建议将上述结构更改为:
documents docmentStatusHistory status
+--------------+ +--------------------+ +----------+
| docId | | docStatusHistoryId | | statusId |
+--------------+ +--------------------+ +----------+
| currStatusId | | docId | | ... |
| ... | | statusId | +----------+
+--------------+ | ... |
+--------------------+
This way we'd have the current status of a document right where it should be.
通过这种方式,我们可以将文档的当前状态放在应有的位置。
Because the way the legacy applications were built I could not change the code on legacy applications to update the current status on the document table.
由于遗留应用程序的构建方式,我无法更改遗留应用程序的代码来更新文档表上的当前状态。
In this case I had to open an exception to my rule to avoid triggers at all costs, simply because I don't have access to the legacy applications code.
在这种情况下,我不得不为我的规则打开一个例外,以不惜一切代价避免触发器,仅仅是因为我无法访问遗留应用程序代码。
I created a trigger that updates the current status of a document every time a new status is added to the status history, and it works like a charm.
我创建了一个触发器,每次将新状态添加到状态历史记录时都会更新文档的当前状态,它的作用就像一个魅力。
However, in an obscure and rarely used situation there is a need to DELETEthe last status history, instead of simply adding a new one. So, I created the following trigger:
然而,在一个晦涩且很少使用的情况下,需要删除最后一个状态历史,而不是简单地添加一个新的。所以,我创建了以下触发器:
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;
And thats where I got the infamous error ORA-04091
.
这就是我得到臭名昭著的错误的地方ORA-04091
。
I understand WHYI'm getting this error, even though I configured the trigger as an AFTERtrigger.
我明白为什么我会收到这个错误,即使我将触发器配置为AFTER触发器。
The thing is that I can't see a way around this error. I have searched the net for a while and couldn't find anything helpful so far.
问题是我看不到解决此错误的方法。我在网上搜索了一段时间,到目前为止找不到任何有用的东西。
In time, we're using Oracle 9i.
随着时间的推移,我们正在使用 Oracle 9i。
回答by Justin Cave
The standard workaround to a mutating table error is to create
变异表错误的标准解决方法是创建
- A package with a collection of keys (i.e. docId's in this case). A temporary table would also work
- A before statement trigger that initializes the collection
- A row-level trigger that populates the collection with each docId that has changed
- An after statement trigger that iterates over the collection and does the actual UPDATE
- 带有一组键的包(即本例中的 docId)。临时表也可以
- 初始化集合的 before 语句触发器
- 使用已更改的每个 docId 填充集合的行级触发器
- 迭代集合并执行实际 UPDATE 的 after 语句触发器
So something like
所以像
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;