1. 程式人生 > >觸發器中ORA-04091 變異表錯誤原因和一般解決方法

觸發器中ORA-04091 變異表錯誤原因和一般解決方法

 在公司寫了一個觸發器,遇到了ORA-04091 錯誤,開始以為是觸發器邏輯寫錯誤了,後來反覆修改和檢查後感覺沒有錯誤,可是還報這個錯誤.

後經過查資料知道原來在觸發器中對觸發器要更新的表是不能在本表上做查詢的,最終我在觸發器中繞過查詢本表就可以了.現在記錄下原因和一般的解決方法:

ORA-04091 產生原因:

一般處理這個錯誤的方法(論壇上整理出來的):

ORA-04091行觸發器中訪問變異表
--這個問題相信很多人都遇到過,我之前在做觸發器的時候也遇到過幾次,解決方法一般就是用兩種,一是僅用自治事務的觸發器就可以解決;二是在觸發器中用臨時變數
--也就是用臨時變數儲存行資訊;當然改變一下涉及思路或許是最好的選擇,但是在遇到既不能改變設計,而且必須用觸發器解決的時候就會有問題了
--下面的就是一個這樣的例子,也是我剛剛在工作中遇到的問題,記錄下來和大家分享一下,對於高手來說不算什麼,但或許對一些人來說還是有點用的。
--表test是測試表,具體需求是:如果更新C列的資料,則觸發更新擁有和更新行相同值的A列,且不同值的B列資料,D欄位可以看成是這個表的主鍵,
--當然沒有這個主鍵欄位也是沒問題的。
SQL> select * from test;

         A          B          C          D                                    
---------- ---------- ---------- ----------                                    
         1          1        200          1                                    
         1          2        100          2                                    
         1          2        100          3                                    
         2          2        300          4                                    
--剛上來想到的觸發器是這樣的
SQL> create or replace trigger upd_index_data_tr
  2    before update of c on test
  3    for each row
  4 
  5  declare
  6    v_a number;
  7 
  8    v_b  number;
  9    v_c  number;
 10    v_r rowid;
 11    vs_c number;
 12 
 13  begin
 14 
 15    if :new.c is not null then
 16      select :new.a, :new.b, :new.c,:new.rowid  into v_a, v_b, v_c,v_r from dual;
 17      ----
 18      select sum(c)+v_c
 19        into vs_c
 20        from test
 21       where b = v_b
 22         and a = v_a and rowid<>v_r;
 23      -----
 24      update test
 25         set c = vs_c
 26       where a = v_a
 27         and b = 1;
 28    end if;
 29 
 30  end;
 31  /

觸發器已建立
--執行更新語句,不出意外的會報錯
SQL> update test set c=123 where d=2;
update test set c=123 where d=2
       *
第 1 行出現錯誤:
ORA-04091: 表 LYH.TEST 發生了變化, 觸發器/函式不能讀它
ORA-06512: 在 "LYH.UPD_INDEX_DATA_TR", line 14
ORA-04088: 觸發器 'LYH.UPD_INDEX_DATA_TR' 執行過程中出錯

--於是想到用自治事務
SQL> create or replace trigger upd_index_data_tr
  2    before update of c on test
  3    for each row
  4 
  5  declare
  6    v_a number;
  7 
  8    v_b  number;
  9    v_c  number;
 10    v_r rowid;
 11    vs_c number;
 12    PRAGMA AUTONOMOUS_TRANSACTION;
 13  begin
 14 
 15    if :new.c is not null then
 16      select :new.a, :new.b, :new.c,:new.rowid  into v_a, v_b, v_c,v_r from dual;
 17      ----
 18      select sum(c)+v_c
 19        into vs_c
 20        from test
 21       where b = v_b
 22         and a = v_a and rowid<>v_r;
 23      -----
 24      update test
 25         set c = vs_c
 26       where a = v_a
 27         and b = 1;
 28    end if;
 29 
 30  end;
 31  /

觸發器已建立
--這裡居然報了死鎖,跟蹤發現原來是執行到第24行的時候,又觸發了觸發器,兩次觸發造成了資源的爭奪。
--當然insert的時候就沒有這樣的問題啦,可這裡還是update。。。
SQL> update test set c=123 where d=2;
update test set c=123 where d=2
       *
第 1 行出現錯誤:
ORA-00060: 等待資源時檢測到死鎖
ORA-06512: 在 "LYH.UPD_INDEX_DATA_TR", line 20
ORA-04088: 觸發器 'LYH.UPD_INDEX_DATA_TR' 執行過程中出錯
ORA-06512: 在 "LYH.UPD_INDEX_DATA_TR", line 20
ORA-04088: 觸發器 'LYH.UPD_INDEX_DATA_TR' 執行過程中出錯

--於是在第15行增加了一個條件,保證第二次觸發的時候,並不會試圖去爭奪已被鎖住的行資源。
SQL> create or replace trigger upd_index_data_tr
  2    before update of c on test
  3    for each row
  4 
  5  declare
  6    v_a number;
  7 
  8    v_b  number;
  9    v_c  number;
 10    v_r rowid;
 11    vs_c number;
 12    PRAGMA AUTONOMOUS_TRANSACTION;
 13  begin
 14 
 15    if :new.c is not null and :new.b<>1 then
 16      select :new.a, :new.b, :new.c,:new.rowid  into v_a, v_b, v_c,v_r from dual;
 17      ----
 18      select sum(c)+v_c
 19        into vs_c
 20        from test
 21       where b = v_b
 22         and a = v_a and rowid<>v_r;
 23      -----
 24      update test
 25         set c = vs_c
 26       where a = v_a
 27         and b = 1;
 28    end if;
 29 
 30  end;
 31  /

觸發器已建立
--這裡的錯就比較好理解了
--加個commit
SQL> update test set c=123 where d=2;
update test set c=123 where d=2
       *
第 1 行出現錯誤:
ORA-06519: 檢測到活動的自治事務處理, 已經回退
ORA-06512: 在 "LYH.UPD_INDEX_DATA_TR", line 26
ORA-04088: 觸發器 'LYH.UPD_INDEX_DATA_TR' 執行過程中出錯


SQL> create or replace trigger upd_index_data_tr
  2    before update of c on test
  3    for each row
  4 
  5  declare
  6    v_a number;
  7 
  8    v_b  number;
  9    v_c  number;
 10    v_r rowid;
 11    vs_c number;
 12    PRAGMA AUTONOMOUS_TRANSACTION;
 13  begin
 14 
 15    if :new.c is not null and :new.b<>1 then
 16      select :new.a, :new.b, :new.c,:new.rowid  into v_a, v_b, v_c,v_r from dual;
 17      ----
 18      select sum(c)+v_c
 19        into vs_c
 20        from test
 21       where b = v_b
 22         and a = v_a and rowid<>v_r;
 23      -----
 24      update test
 25         set c = vs_c
 26       where a = v_a
 27         and b = 1;
 28    end if;
 29  commit; --------commit!!!
 30  end;
 31  /

觸發器已建立

SQL> update test set c=123 where d=2;

已更新 1 行。
--成功
SQL> select * from test;

         A          B          C          D                                    
---------- ---------- ---------- ----------                                    
         1          1        223          1                                    
         1          2        123          2                                    
         1          2        100          3                                    
         2          2        300          4                                    

SQL> spool off;
--當然,這個例子是適合只能更新欄位B=2的需求,如果沒有限制就得稍微改動一下第15行的條件。