ORA-04091: table xxxx is mutating, trigger/function may not see it
今天同事让我看一个触发器为什么老是报错,当执行DML语句触发触发器后,会报ORA-04091错误:ORA-04091: table xxxx is mutating, trigger/function may not see it 。对应的中文错误提示为:ORA-04091: 表 xxx发生了变化, 触发器/函数不能读它。
原因分析:
[oracle@DB-Server ~]$ oerr ora 4091
04091, 00000, "table %s.%s is mutating, trigger/function may not see it"
// *Cause: A trigger (or a user defined plsql function that is referenced in
// this statement) attempted to look at (or modify) a table that was
// in the middle of being modified by the statement which fired it.
// *Action: Rewrite the trigger (or function) so it does not read that table.
出现这个错误,是因为在行级触发器中,不能查询自身表。即在某表的行级触发器中不能读取当前表的问题。ORACLE行级触发器(FOR EACH ROW),不能对本表做任何操作,甚至包括读取。
解决方法:
1:最简单的解决方法,通过自治事务解决这个错误,即在触发器里面加上:PRAGMA AUTONOMOUS_TRANSACTION 它表示当前的触发器作为已有事务的子事务运行,子事务自治管理,子事务的commit、rollback操作不影响父事务的状态。
当然也有其它一些技巧解决这个问题,可以看看Tom大师关于解决ORA-04091的一些方法: https://asktom.oracle.com/pls/apex/ASKTOM.download_file?p_file=6551198119097816936
Avoiding Mutating Tables
Ok, so you've just recieved the error:
ORA-04091: table XXXX is mutating, trigger/function may not see it
and you want to get around that. This short article will describe and demonstrate the various methods of getting around the mutating table error.
If you are interested in why you are getting it and in what cases you will get it, please see the Oracle Server Application Developers Guide (click here to read it right now -- this link is to technet.oracle.com. You need a password to access this site but you can get one right away for free).
Avoiding the mutating table error is fairly easy. We must defer processing against the mutating or constrainng table until an AFTER trigger. We will consider two cases:
- Hitting the ORA-4091 in an INSERT trigger or an UPDATE trigger where you only need access to the :new values
- Hitting the ORA-4091 in a DELETE trigger or an UPDATE trigger where you need to access the :old values
Case 1 - you only need to access the :new values
This case is the simplest. What we will do is capture the ROWIDS of the inserted or udpated rows. We can then use these ROWIDS in an AFTER trigger to query up the affected rows.
It always takes 3 triggers to work around the mutating table error. They are:
- A before trigger to set the package state to a known, consistent state
- An after, row level trigger to capture each rows changes
- An after trigger to actually process the change.
As an example -- to show how to do this, we will attempt to answer the following question:
I have a table containing a key/status/effective date combination. When status
changes, the values are propagated by trigger to a log table recording the
status history. When no RI constraint is in place everything works fine.When an RI trigger enforces a parent-child relationship, the status change
logging trigger fails because the parent table is mutating. Propagating the
values to the log table implicitly generates a lookup back to the parent table
to ensure the RI constraint is satisfied.I do not want to drop the RI constraint. I realize that the status is
denormalized. I want it that way. What is a good way to maintain the log?
Here is the implementation:
SQL> create table parent
2 ( theKey int primary key,
3 status varchar2(1),
4 effDate date
5 )
6 /
Table created.
SQL> create table log_table
2 ( theKey int references parent(theKey),
3 status varchar2(1),
4 effDate date
5 )
6 /
Table created.
SQL> REM this package is used to maintain our state. We will save the rowids of newly
SQL> REM inserted / updated rows in this package. We declare 2 arrays -- one will
SQL> REM hold our new rows rowids (newRows). The other is used to reset this array,
SQL> REM it is an 'empty' array
SQL> create or replace package state_pkg
2 as
3 type ridArray is table of rowid index by binary_integer;
4
4 newRows ridArray;
5 empty ridArray;
6 end;
7 /
Package created.
SQL> REM We must set the state of the above package to some known, consistent state
SQL> REM before we being processing the row triggers. This trigger is mandatory,
SQL> REM we *cannot* rely on the AFTER trigger to reset the package state. This
SQL> REM is because during a multi-row insert or update, the ROW trigger may fire
SQL> REM but the AFTER tirgger does not have to fire -- if the second row in an update
SQL> REM fails due to some constraint error -- the row trigger will have fired 2 times
SQL> REM but the AFTER trigger (which we relied on to reset the package) will never fire.
SQL> REM That would leave 2 erroneous rowids in the newRows array for the next insert/update
SQL> REM to see. Therefore, before the insert / update takes place, we 'reset'
SQL> create or replace trigger parent_bi
2 before insert or update on parent
3 begin
4 state_pkg.newRows := state_pkg.empty;
5 end;
6 /
Trigger created.
SQL> REM This trigger simply captures the rowid of the affected row and
SQL> REM saves it in the newRows array.
SQL> create or replace trigger parent_aifer
2 after insert or update of status on parent for each row
3 begin
4 state_pkg.newRows( state_pkg.newRows.count+1 ) := :new.rowid;
5 end;
6 /
Trigger created.
SQL> REM this trigger processes the new rows. We simply loop over the newRows
SQL> REM array processing each newly inserted/modified row in turn.
SQL> create or replace trigger parent_ai
2 after insert or update of status on parent
3 begin
4 for i in 1 .. state_pkg.newRows.count loop
5 insert into log_table
6 select theKey, status, effDate
7 from parent where rowid = state_pkg.newRows(i);
8 end loop;
9 end;
10 /
Trigger created.
SQL> REM this demonstrates that we can process single and multi-row inserts/updates
SQL> REM without failure (and can do it correctly)
SQL> insert into parent values ( 1, 'A', sysdate-5 );
1 row created.
SQL> insert into parent values ( 2, 'B', sysdate-4 );
1 row created.
SQL> insert into parent values ( 3, 'C', sysdate-3 );
1 row created.
SQL> insert into parent select theKey+6, status, effDate+1 from parent;
3 rows created.
SQL> select * from log_table;
THEKEY S EFFDATE
---------- - ---------
1 A 04-AUG-99
2 B 05-AUG-99
3 C 06-AUG-99
7 A 05-AUG-99
8 B 06-AUG-99
9 C 07-AUG-99
6 rows selected.
SQL> update parent set status = chr( ascii(status)+1 ), effDate = sysdate;
6 rows updated.
SQL> select * from log_table;
THEKEY S EFFDATE
---------- - ---------
1 A 04-AUG-99
2 B 05-AUG-99
3 C 06-AUG-99
7 A 05-AUG-99
8 B 06-AUG-99
9 C 07-AUG-99
1 B 09-AUG-99
2 C 09-AUG-99
3 D 09-AUG-99
7 B 09-AUG-99
8 C 09-AUG-99
9 D 09-AUG-99
12 rows selected.
Case 2 - you need to access the :old values
This one is a little more involved but the concept is the same. We'll save the actual OLD values in an array (as opposed to just the rowids of the new rows). Using tables of records this is fairly straightforward. Lets say we wanted to implement a flag delete of data -- that is, instead of actually deleting the record, you would like to set a date field to SYSDATE and keep the record in the table (but hide it from queries). We need to 'undo' the delete.
In Oracle8.0 and up, we could use "INSTEAD OF" triggers on a view to do this, but in 7.3 the implementation would look like this:
SQL> REM this is the table we will be flag deleting from.
SQL> REM No one will ever access this table directly, rather,
SQL> REM they will perform all insert/update/delete/selects against
SQL> REM a view on this table..
SQL> create table delete_demo ( a int,
2 b date,
3 c varchar2(10),
4 hidden_date date default to_date( '01-01-0001', 'DD-MM-YYYY' ),
5 primary key(a,hidden_date) )
6 /
Table created.
SQL> REM this is our view. All DML will take place on the view, the table
SQL> REM will not be touched.
SQL> create or replace view delete_demo_view as
2 select a, b, c from delete_demo where hidden_date = to_date( '01-01-0001', 'DD-MM-YYYY' )
3 /
View created.
SQL> grant all on delete_demo_view to public
2 /
Grant succeeded.
SQL> REM here is the state package again. This time the array is of
SQL> REM TABLE%ROWTYPE -- not just a rowid
SQL> create or replace package delete_demo_pkg
2 as
3 type array is table of delete_demo%rowtype index by binary_integer;
4
4 oldvals array;
5 empty array;
6 end;
7 /
Package created.
SQL> REM the reset trigger...
SQL> create or replace trigger delete_demo_bd
2 before delete on delete_demo
3 begin
4 delete_demo_pkg.oldvals := delete_demo_pkg.empty;
5 end;
6 /
Trigger created.
SQL> REM Here, instead of capturing the rowid, we must capture the before image
SQL> REM of the row.
SQL> REM We cannot really undo the delete here, we are just capturing the deleted
SQL> REM data
SQL> create or replace trigger delete_demo_bdfer
2 before delete on delete_demo
3 for each row
4 declare
5 i number default delete_demo_pkg.oldvals.count+1;
6 begin
7 delete_demo_pkg.oldvals(i).a := :old.a;
8 delete_demo_pkg.oldvals(i).b := :old.b;
9 delete_demo_pkg.oldvals(i).c := :old.c;
10 end;
11 /
Trigger created.
SQL> REM Now, we can put the deleted data back into the table. We put SYSDATE
SQL> REM in as the hidden_date field -- that shows us when the record was deleted.
SQL> create or replace trigger delete_demo_ad
2 after delete on delete_demo
3 begin
4 for i in 1 .. delete_demo_pkg.oldvals.count loop
5 insert into delete_demo ( a, b, c, hidden_date )
6 values
7 ( delete_demo_pkg.oldvals(i).a, delete_demo_pkg.oldvals(i).b,
8 delete_demo_pkg.oldvals(i).c, sysdate );
9 end loop;
10 end;
11 /
Trigger created.
SQL> REM Now, to show it at work...
SQL> insert into delete_demo_view values ( 1, sysdate, 'Hello' );
1 row created.
SQL> insert into delete_demo_view values ( 2, sysdate, 'Goodbye' );
1 row created.
SQL> select * from delete_demo_view;
A B C
---------- --------- ----------
1 09-AUG-99 Hello
2 09-AUG-99 Goodbye
SQL> delete from delete_demo_view;
2 rows deleted.
SQL> select * from delete_demo_view;
no rows selected
SQL> select * from delete_demo;
A B C HIDDEN_DA
---------- --------- ---------- ---------
1 09-AUG-99 Hello 09-AUG-99
2 09-AUG-99 Goodbye 09-AUG-99
All information and materials provided here are provided "as-is"; Oracle disclaims all express and implied warranties, including, the implied warranties of merchantability or fitness for a particular use. Oracle shall not be liable for any damages, including, direct, indirect, incidental, special or consequential damages for loss of profits, revenue, data or data use, incurred by you or any third party in connection with the use of this information or these materials.
ORA-04091: table xxxx is mutating, trigger/function may not see it的更多相关文章
- ORA-04091: table xxx is mutating, trigger/function may not see it
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 Connected as tbcs SQL> SQL ...
- ORA-04091: table is mutating, trigger/function may not see it
今天在论坛里发现了一个关于ORA-04091的老帖子,收获良多,特此整理一下 关于ORA-04091: table is mutating, trigger/function may not see ...
- 错误"ORA-04091: table is mutating, trigger/function may not see it"的原因以及解决办法
错误的原因该错误是在编写trigger时常遇到的问题,其根本原因是由于对本表的操作造成的.对于使用了for each row 的触发器,做了DML操作(delete,update,insert),还没 ...
- PostgreSQL trigger (function) examples
postgres=# \c warehouse_db You are now connected to database "warehouse_db" as user " ...
- Mysql:The table‘xxxx’is full
下午跑程序,在插入mysql时突然报错: "The table'xxxx'is full" 而之前一直没问题的. 上网查了一下,都说临时表的问题,需要设置"tmp_tab ...
- [mysql] 修复问题表Table '.xxxx' is marked as crashed and should be repaired
程序执行的过程中,出现 Table '.xxxx' is marked as crashed and should be repaired 错误,上网查了一下,原来是表遭到损坏所致,具体修复办法如 ...
- Mysql drop function xxxx ERROR 1305 (42000): FUNCTION (UDF) xxxx does not exist
mysql> drop function GetEmployeeInformationByID;ERROR 1305 (42000): FUNCTION (UDF) GetEmployeeInf ...
- 【Azure 应用服务】部署Kafka Trigger Function到Azure Function服务中,解决自定义域名解析难题
问题描述 经过前两篇文章,分别使用VM搭建了Kafka服务,创建了Azure Function项目,并且都在本地运行成功. [Azure Developer]在Azure VM (Windows) 中 ...
- Error: [ng:areq] Argument 'xxxx' is not a function, got undefined
"Error: [ng:areq] Argument 'keywords' is not a function, got undefined" 代码类似这样的: <div n ...
随机推荐
- ASP.NET网站优化(转自一位博友的文章,写的非常好)
不修改代码就能优化ASP.NET网站性能的一些方法 阅读目录 开始 配置OutputCache 启用内容过期 解决资源文件升级问题 启用压缩 删除无用的HttpModule 其它优化选项 本文将介绍一 ...
- aud$定位错误用户密码登陆数据库的具体信息
环境:Oracle 11.2.0.3 客户端使用错误的用户密码登陆数据库 查询最近1天由于密码错误登陆失败的信息 查询当前审计中有哪些returncode值 1. 客户端使用错误的用户密码登陆数据库 ...
- HTML5 视频(二) <video> 使用 DOM 进行控制
HTML5 <video> 使用 DOM 进行控制 一.HTML5 <video> 元素同样拥有方法.属性和事件. 其中的方法用于播放.暂停以及加载等.其中的属性(比如时长.音 ...
- PHP实现新浪长链接转化成短链接API
我们经常收到类似于这样的短信(如下图),发现其中的链接并不是常规的网址链接,而是个短小精悍的短链接,产品中经常需要这样的需求,如果在给用户下发的短信中是一个很长的连接,用户体验肯定很差,因此我们需要实 ...
- linux全方位掌握一个命令--思路比方法更重要
Linux命令众多,当不清楚一个命令的使用方法时,我们该怎样了解命令的属性和帮助? 1.用type命令了解一个命令的属性 [root@zejin240 testdir]# type cd cd ...
- Xamarin.Android之Splash的几种简单实现
对现在的APP软件来说,基本上都会有一个Splash页面,类似大家常说的欢迎页面.启动界面之类的. 正常来说这个页面都会有一些相关的信息,比如一些理念,Logo,版本信息等 下面就来看看在Xamari ...
- 芒果TV招聘研发工程师(JAVA PYTHON),地点长沙
长沙芒果TV招聘高级 JAVA Python 工程师,工作地点:湖南广电 有兴趣的邮件0xmalloc@gmail.com; zealotyin@qq.com 公司有一大批从北京上海一线互联网企业 ...
- 学习笔记之-------UIScrollView 基本用法 代理使用
//contentSize.contentInset和contentOffset 是 scrollView三个基本的属性. // 滚动 self.ScrollView.contentSize =sel ...
- DDD心得
使用DDD分层架构有哪些好处 帮你更集中的管理业务逻辑. 帮你降低各层间,以及各业务模块间的依赖关系. 帮你更方便的进行单元测试. 我的DDD分层架构使用经验 使用充血模型,将业务逻辑尽量放到领域实体 ...
- python之初体验
1. Python简介: Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年发明, ...