ORA-04091: 表 [blah] 正在发生变化,触发器/函数可能看不到它
我最近开始开发一个大型复杂的应用程序,由于此错误,我刚刚被分配了一个错误:有
ORA-04091: table SCMA.TBL1 is mutating, trigger/function may not see it
ORA-06512: at "SCMA.TRG_T1_TBL1_COL1", line 4
ORA-04088: error during execution of trigger 'SCMA.TRG_T1_TBL1_COL1'
问题的触发器看起来像
create or replace TRIGGER TRG_T1_TBL1_COL1
BEFORE INSERT OR UPDATE OF t1_appnt_evnt_id ON TBL1
FOR EACH ROW
WHEN (NEW.t1_prnt_t1_pk is not null)
DECLARE
v_reassign_count number(20);
BEGIN
select count(t1_pk) INTO v_reassign_count from TBL1
where t1_appnt_evnt_id=:new.t1_appnt_evnt_id and t1_prnt_t1_pk is not null;
IF (v_reassign_count > 0) THEN
RAISE_APPLICATION_ERROR(-20013, 'Multiple reassignments not allowed');
END IF;
END;
表有一个主键“t1_pk
”,一个“约会”事件编号” t1_appnt_evnt_id
和另一列“t1_prnt_t1_pk
”可能会或可能 不包含另一行的t1_pk
。
看来触发器正在试图确保没有其他人拥有 相同的 t1_appnt_evnt_id
已引用此行引用的同一行 引用另一行(如果此行引用另一行)。
DBA 对错误报告的评论说“删除触发器,并在代码中执行检查”,但不幸的是他们在 Hibernate 之上有一个专有的代码生成框架,所以我什至不知道它实际上在哪里被写出来,所以我希望有一种方法可以让这个触发器发挥作用。 有没有?
I recently started working on a large complex application, and I've just been assigned a bug due to this error:
ORA-04091: table SCMA.TBL1 is mutating, trigger/function may not see it
ORA-06512: at "SCMA.TRG_T1_TBL1_COL1", line 4
ORA-04088: error during execution of trigger 'SCMA.TRG_T1_TBL1_COL1'
The trigger in question looks like
create or replace TRIGGER TRG_T1_TBL1_COL1
BEFORE INSERT OR UPDATE OF t1_appnt_evnt_id ON TBL1
FOR EACH ROW
WHEN (NEW.t1_prnt_t1_pk is not null)
DECLARE
v_reassign_count number(20);
BEGIN
select count(t1_pk) INTO v_reassign_count from TBL1
where t1_appnt_evnt_id=:new.t1_appnt_evnt_id and t1_prnt_t1_pk is not null;
IF (v_reassign_count > 0) THEN
RAISE_APPLICATION_ERROR(-20013, 'Multiple reassignments not allowed');
END IF;
END;
The table has a primary key "t1_pk
", an "appointment event id"t1_appnt_evnt_id
and another column "t1_prnt_t1_pk
" which may or may
not contain another row's t1_pk
.
It appears the trigger is trying to make sure that nobody else with the
same t1_appnt_evnt_id
has referred to the same one this row is referring to a referral to another row, if this one is referring to another row.
The comment on the bug report from the DBA says "remove the trigger, and perform the check in the code", but unfortunately they have a proprietary code generation framework layered on top of Hibernate, so I can't even figure out where it actually gets written out, so I'm hoping that there is a way to make this trigger work. Is there?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我想我不同意你对触发器试图做什么的描述
做。 在我看来,它是为了强制执行这个业务规则:
给定 t1_appnt_event 值,只有一行可以有非 NULL 值
一次 t1_prnt_t1_pk。 (它们在第二列中是否具有相同的值并不重要。)
有趣的是,它是为 UPDATE OF t1_appnt_event 定义的,但不是为其他列定义的,所以我认为有人可以通过更新第二列来打破规则,除非该列有单独的触发器。
可能有一种方法可以创建一个基于函数的索引来强制执行此规则,以便您可以完全摆脱触发器。 我想出了一种方法,但它需要一些假设:
如果这些假设成立,您可以创建一个像这样的函数:
和一个像这样的索引:
因此,PMNT 列为 NULL 的行将出现在索引中,并将主键的倒数作为第二个值,因此它们永远不会相互冲突。 非 NULL 的行将使用该列的实际(正)值。 发生约束冲突的唯一方法是两行的两列中具有相同的非 NULL 值。
这可能过于“聪明”,但它可能会帮助您解决问题。
Paul Tomblin 的更新:我对 igor 在评论中提出的原始想法进行了更新:
I think I disagree with your description of what the trigger is trying to
do. It looks to me like it is meant to enforce this business rule: For a
given value of t1_appnt_event, only one row can have a non-NULL value of
t1_prnt_t1_pk at a time. (It doesn't matter if they have the same value in the second column or not.)
Interestingly, it is defined for UPDATE OF t1_appnt_event but not for the other column, so I think someone could break the rule by updating the second column, unless there is a separate trigger for that column.
There might be a way you could create a function-based index that enforces this rule so you can get rid of the trigger entirely. I came up with one way but it requires some assumptions:
If these assumptions are true, you could create a function like this:
and an index like this:
So rows where the PMNT column is NULL would appear in the index with the inverse of the primary key as the second value, so they would never conflict with each other. Rows where it is not NULL would use the actual (positive) value of the column. The only way you could get a constraint violation would be if two rows had the same non-NULL values in both columns.
This is perhaps overly "clever", but it might help you get around your problem.
Update from Paul Tomblin: I went with the update to the original idea that igor put in the comments:
我同意 Dave 的观点,即可以而且应该使用内置约束(例如唯一索引(或唯一约束))来实现所需的结果。
如果您确实需要解决变异表错误,通常的方法是创建一个包,其中包含一个包范围的变量,该变量是一个可用于识别更改的行的表(我认为 ROWID 是可能,否则你必须使用PK,我目前不使用Oracle,所以我无法测试它)。 然后,FOR EACH ROW 触发器用该语句修改的所有行填充此变量,然后有一个 AFTER every 语句触发器读取这些行并验证它们。
像这样的东西(语法可能是错误的,我已经有几年没有使用 Oracle 了)
I agree with Dave that the desired result probalby can and should be achieved using built-in constraints such as unique indexes (or unique constraints).
If you really need to get around the mutating table error, the usual way to do it is to create a package which contains a package-scoped variable that is a table of something that can be used to identify the changed rows (I think ROWID is possible, otherwise you have to use the PK, I don't use Oracle currently so I can't test it). The FOR EACH ROW trigger then fills in this variable with all rows that are modified by the statement, and then there is an AFTER each statement trigger that reads the rows and validate them.
Something like (syntax is probably wrong, I haven't worked with Oracle for a few years)
对于任何基于触发器(或基于应用程序代码)的解决方案,您需要
设置锁定以防止多用户环境中的数据损坏。
即使您的触发器有效,或者被重写以避免变异表
问题,它不会阻止 2 个用户同时更新
t1_appnt_evnt_id 为非 t1_appnt_evnt_id 的行上的相同值
null:假设当前没有 t1_appnt_evnt_id=123 的行并且
t1_prnt_t1_pk 不为空:
您现在的数据库已损坏!
避免这种情况的方法(在触发器或应用程序代码中)是锁定
在执行检查之前,t1_appnt_evnt_id=123 引用的表中的父行:
现在会话 2 的触发器必须等待会话 1 提交或回滚,然后才能执行检查。
实施 Dave Costa 的索引会更简单、更安全!
最后,我很高兴没有人建议将 PRAGMA AUTONOMOUS_TRANSACTION 添加到您的触发器中:这经常在论坛上建议,并且只要变异表问题消失即可起作用 - 但它使数据完整性问题变得更糟! 所以只是不要...
With any trigger-based (or application code-based) solution you need to
put in locking to prevent data corruption in a multi-user environment.
Even if your trigger worked, or was re-written to avoid the mutating table
issue, it would not prevent 2 users from simultaneously updating
t1_appnt_evnt_id to the same value on rows where t1_appnt_evnt_id is not
null: assume there are currenly no rows where t1_appnt_evnt_id=123 and
t1_prnt_t1_pk is not null:
You now have a corrupted database!
The way to avoid this (in trigger or application code) would be to lock
the parent row in the table referenced by t1_appnt_evnt_id=123 before performing the check:
Now session 2's trigger must wait for session 1 to commit or rollback before it performs the check.
It would be much simpler and safer to implement Dave Costa's index!
Finally, I'm glad no one has suggested adding PRAGMA AUTONOMOUS_TRANSACTION to your trigger: this is often suggested on forums and works in as much as the mutating table issue goes away - but it makes the data integrity problem even worse! So just don't...
我在使用 Hibernate 时也遇到了类似的错误。 通过使用冲洗会话
为我解决了这个问题。 (我没有发布我的代码块,因为我确信所有内容都已正确编写并且应该可以工作 - 但直到我添加了前面的flush() 语句后才开始工作)。 也许这可以帮助某人。
I had similar error with Hibernate. And flushing session by using
solved this problem for me. (I'm not posting my code block as I was sure that everything was written properly and should work - but it did not until I added the previous flush() statement). Maybe this can help someone.