变异表的问题

问题

在论坛中经常遇到变异表的问题,我收藏了一片有关变异表问题的文章,现在把他放在这里,希望能和大家分享!

 

   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
 
 
 

时间: 2024-07-31 08:20:29

变异表的问题的相关文章

Oracle变异表解决方法--ORA-04091错误

Oracle变异表解决方法--ORA-04091错误 原因:触发器(或者被语句中引用的用户自定义PL/SQL函数)视图去查询(或修改)一个被另一语句修改而触发的表. 解决方法:重写触发器(或函数)避免读该表. 对表进行DELETE,UPDATE,INSERT操作时,所操作的表就变成了变异表,对表的行级(FOR EACH ROW)触发器中不能对该表进行DML操作.这时可以使用两个方法解决. [@more@] 1.采用自治事务pragma autonomous_transaction解决. 下面给出

PL/SQL --> INSTEAD OF 触发器

--============================== -- PL/SQL --> INSTEAD OF 触发器 --==============================       INSTEAD OF 触发器常用于管理编写不可更新的视图,INSTEAD-OF触发器必须是行级的.     可以用INSTEAD OF触发器来解释INSERT.UPDATE和DELETE语句,并用备用的程序代码替换那些指令.   一.不可更新视图     基于下列情形创建的视图,不可直接对其进行D

PL/SQL12.2 ——DML 触发器

http://blog.csdn.net/robinson_0612/article/details/6098263  INSTEAD OF 触发器常用于管理编写不可更新的视图,INSTEAD-OF触发器必须是行级的.     可以用INSTEAD OF触发器来解释INSERT.UPDATE和DELETE语句,并用备用的程序代码替换那些指令.   一.不可更新视图     基于下列情形创建的视图,不可直接对其进行DML操作         使用了集合操作运算符(UNION,UNION ALL ,

[20140311]等待事件enq HW - contention

[20140311]等待事件enq HW - contention.txt 生产系统业务高峰时出现enq: HW - contention,一般这个主要是插入记录非常密集的情况下出现,自己对系统分析看看主要是那些对象 引起的问题. SQL> @ver BANNER ---------------------------------------------------------------- Oracle Database 10g Enterprise Edition Release 10.2.

小麦苗BLOG文章索引

小麦苗BLOG文章索引            自从2014年7月1号开始写blog到2015年5月5日,历时10个月的时间,大概写了90篇文章,这blog多了就乱了,今天抽空出来整理整理,方便大家也方便自己阅读,本文将一直更新,另外,最后我把所有的blog文章全列出来,可能会有用.    小麦苗的所有文章:itpub文章链接-小麦苗.zip     2015年06月03日更新一次,我写的blog数量:109 篇    2015年07月03日更新一次,我写的blog数量:126 篇    2016

Oracle 隐含参数

Oracle 隐含参数 点击(此处)折叠或打开 set pagesize 9999 set line 9999 col NAME format a40 col KSPPDESC format a50 col KSPPSTVL format a20 SELECT a.INDX,        a.KSPPINM NAME,        a.KSPPDESC,        b.KSPPSTVL FROM x$ksppi a,        x$ksppcv b WHERE a.INDX = b.

RAC性能分析gc buffer busy acquire 等待事件

概述---------------------gc buffer busy是RAC数据库中常见的等待事件,11g开始gc buffer  busy分为gc buffer busy acquire和gc buffer  busy release. gc buffer busy acquire是当session#1尝试请求访问远程实例(remote  instance) buffer,但是在session#1之前已经有相同实例上另外一个session#2请求访问了相同的buffer,并且没有完成,那

关于enq: TX - allocate ITL entry的问题分析

今天发现系统在下午1点左右的时候负载比较高,就抓取了一个最新的awr报告. Snap Id Snap Time Sessions Cursors/Session Begin Snap: 20892 26-Nov-14 13:20:17 3623 5.4 End Snap: 20893 26-Nov-14 13:30:17 3602 5.4 Elapsed:   10.01 (mins)     DB Time:   365.56 (mins)     来看看是否有top的等待事件,对于其它的几个

Oracle中REGEXP_SUBSTR函数

Oracle中REGEXP_SUBSTR函数的使用说明:   在oracle中,使用一条语句实现将'17,20,23'拆分成'17','20','23'的集合.   REGEXP_SUBSTR函数格式如下: function REGEXP_SUBSTR(String, pattern, position, occurrence, modifier) __srcstr     :需要进行正则处理的字符串 __pattern    :进行匹配的正则表达式 __position   :起始位置,从第几