緣由:觸發器(或者被語句中引用的用戶自定義PL/SQL函數)視圖去查詢(或修改)一個被另外一語句修改而觸發的表。html
解決方法:重寫觸發器(或函數)避免讀該表。linux
對錶進行DELETE,UPDATE,INSERT操做時,所操做的表就變成了變異表,對錶的行級(FOR EACH ROW)觸發器中不能對該表進行DML操做。這時可使用兩個方法解決。sql
[@more@]express
一、採用自治事務pragma autonomous_transaction解決。oracle
下面給出一個使用自治事務解決ORA-04091錯誤的示例:ide
CREATE OR REPLACE TRIGGER SCOTT.TRG_UPDATE_EMP函數
AFTER UPDATE ON SCOTT.EMPoop
FOR EACH ROW網站
DECLAREui
V_NUM NUMBER;
BEGIN
SELECT COUNT(1) INTO V_NUM FROM SCOTT.EMP T WHERE DEPTNO = :NEW.DEPTNO;
IF V_NUM > 2 THEN
RAISE_APPLICATION_ERROR(-20001, V_NUM);
END IF;
END;
執行更新操做報錯ORA-04091:
SYS@lhrdb> UPDATE SCOTT.EMP SET SAL=0 ;
UPDATE scott.emp set sal=0
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_UPDATE_EMP", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_UPDATE_EMP'
這裏加上自治事務後能夠解決該問題:
CREATE OR REPLACE TRIGGER SCOTT.TRG_UPDATE_EMP
AFTER UPDATE ON SCOTT.EMP
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
V_NUM NUMBER;
BEGIN
SELECT COUNT(1) INTO V_NUM FROM SCOTT.EMP T WHERE DEPTNO = :NEW.DEPTNO;
IF V_NUM > 2 THEN
RAISE_APPLICATION_ERROR(-20001, V_NUM);
END IF;
END;
再次執行:
SYS@lhrdb> UPDATE SCOTT.EMP SET SAL=0 ;
UPDATE SCOTT.EMP SET SAL=0
*
ERROR at line 1:
ORA-20001: 4
ORA-06512: at "SCOTT.TRG_UPDATE_EMP", line 7
ORA-04088: error during execution of trigger 'SCOTT.TRG_UPDATE_EMP'
------------------------------------------------
使用scott方案,建立一下表、觸發器:
[sql] view plain copy
3.緣由分析:
在Oracle中執行DML語句的時候是須要顯示進行提交操做的。當咱們進行插入的時候,會觸發觸發器執行對觸發器做用表和擴展表的種種操做,可是這個時候觸發器和插入語句是在同一個事務管理中的,所以在插入語句沒有被提交的狀況下,咱們沒法對觸發器做用表進行其餘額外的操做。若是執行其餘額外的操做則會拋出如上異常信息。
4.解決方案:
1) 咱們知道,出錯的緣由是由於觸發器和DML語句在同一事務管理中,因此方案一即是將觸發器和DML語句分紅兩個單獨的事務處理。這裏可使用Pragma autonomous_transaction; 告訴Oracle觸發器是自定義事務處理。
SQL語句以下:
[sql] view plain copy
注:以上語句並不能實時得到更新的值。。。緣由是咱們在update emp表後還沒來得及提交sal就觸發了觸發器,這個時候獲取到的只能是老的sal值。
2) 在Oracle Trigger中有:new,:old兩個特殊變量,當觸發器爲行級觸發器的時候,觸發器就會提供new和old兩個保存臨時行數據的特殊變量,咱們能夠從倆個特殊的變量中取出數據執行擴張表的DML操做。
SQL語句以下:
[sql] view plain copy
5. 再次插入數據:
[sql] view plain copy
[sql] view plain copy
二、採用兩個觸發器(一個行級觸發器FOR EACH ROW,一個表級觸發器)和一個包來解決。
CREATE OR REPLACE PACKAGE ADATA AS
TYPE T_AID IS TABLE OF A.AID%TYPE INDEX BY BINARY_INTEGER;
TYPE T_ANO IS TABLE OF A.ANO%TYPE INDEX BY BINARY_INTEGER;
V_AID T_AID;
V_ANO T_ANO;
V_NUMENTRIES BINARY_INTEGER := 0;
END ADATA;
---------------------------------------
CREATE OR REPLACE TRIGGER TR_A
BEFORE INSERT OR UPDATE OF AID ON A
FOR EACH ROW
BEGIN
ADATA.V_NUMENTRIES := ADATA.V_NUMENTRIES + 1;
ADATA.V_AID(ADATA.V_NUMENTRIES) := :NEW.AID;
ADATA.V_ANO(ADATA.V_NUMENTRIES) := :NEW.ANO;
END TR_A;
----------------------------------------
CREATE OR REPLACE TRIGGER TR_B
AFTER INSERT OR UPDATE OF AID ON A
BEGIN
V_MAX CONSTANT NUMBER := 5;
V_CUR NUMBER;
V_AID A.AID%TYPE;
V_ANO A.ANO%TYPE;
BEGIN
FOR V_LOOPINDEX IN 1 .. ADATA.V_NUMENTRIES LOOP
V_AID := ADATA.V_AID(V_LOOPINDEX);
V_ANO := ADATA.V_ANO(V_LOOPINDEX);
SELECT COUNT(*) INTO V_CUR FROM A WHERE AID = V_AID;
IF V_CUR > V_MAXS THEN
RAISE_APPLICATION_ERROR(-20000,
'Too many students for major ' || V_AID ||
'because of student ' || V_ANO);
END IF;
END LOOP;
END TR_B;
變異表是指激發觸發器的DML語句所操做的表
當對一個表建立行級觸發器時,有下列兩條限制:
1.不能讀取或修改任何觸發語句的變異表;
2.不能讀取或修改觸發表的一個約束表的PRIMARY KEY,UNIQUE 或FOREIGN KEY關鍵字的列, 但能夠修改其餘列
例如:有這樣一個需求:在更新員工所在部門或向部門插入新員工時,部門中員工人數不超過7人
若是按照下面的觸發器寫就會使UPDATE操做時報錯
CREATE OR REPLACE TRIGGER updatetrigger
BEFORE UPDATE ON EMP
FOR EACH ROW
DECLARE
v_num NUMBER;
BEGIN
SELECT count(*) INTO v_num FROM emp
WHERE deptno = :new.deptno;
IF (v_num > 7) THEN
RAISE_APPLICATION_ERROR(-20001,
'員工數多於'||v_num);
END IF;
END updatetrigger;
ORA-04091: 表 SCOTT.EMP 發生了變化, 觸發器/函數不能讀它
ORA-06512: 在 "SCOTT.UPDATETRIGGER", line 4
ORA-04088: 觸發器 'SCOTT.UPDATETRIGGER' 執行過程當中出錯
若是既想更新變異表,同時又須要查詢變異表,那麼如何處理呢?
將行級觸發器與語句級觸發器結合起來,在行級觸發器中獲取要修改的記錄的信息,存放到一個軟件包的全局變量中,而後在語句級後觸發器中利用軟件包中全局變量信息對變異表的查詢,並根據查詢的結果進行業務處理
例如:
爲了實如今更新員工所在部門或向部門插入新員工時,部門中員工人數不超過7人,能夠在emp表上建立兩個觸發器,同時建立一個共享信息的包
CREATE OR REPLACE PACKAGE mutate_pkg
AS
v_deptno NUMBER(2);
END;
CREATE OR REPLACE TRIGGER rmutate_trigger
BEFORE INSERT OR UPDATE OF deptno ON EMP
FOR EACH ROW
BEGIN
mutate_pkg.v_deptno:=:new.deptno;
END;
CREATE OR REPLACE TRIGGER smutate_trigger
AFTER INSERT OR UPDATE OF deptno ON EMP
DECLARE
v_num number(3);
BEGIN
SELECT count(*) INTO v_num FROM emp
WHERE deptno = mutate_pkg.v_deptno;
IF v_num>7 THEN
RAISE_APPLICATION_ERROR(-20003,'這部門的員工太多了 '||
mutate_pkg.v_deptno);
END IF;
END;
這樣操做,就不會報ORA-04091: 表SCOTT.EMP 發生了變化,觸發器/函數不能讀它錯誤了。
本篇文章來源於 Linux公社網站(www.linuxidc.com) 原文連接:http://www.linuxidc.com/Linux/2013-11/93146.htm
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:
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:
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.
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.