2008-12-17 40 views
6

最近,我開始在大型複雜應用程序的工作,而我剛剛被分配了一個錯誤,由於這個錯誤:ORA-04091:表[胡說]在變異,觸發/功能可能無法看到它

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」,一個「約會事件ID」 t1_appnt_evnt_id和另一列「t1_prnt_t1_pk」,這可能會或可能 不含其他排的t1_pk

這似乎觸發正試圖確保與 相同t1_appnt_evnt_id別人沒 提到了同一個該行指的是 轉診到另一行,如果這個人是指到另一行。

對DBA錯誤報告的評論說:「刪除觸發器,並執行代碼檢查」,但不幸的是他們有一個專有的代碼生成框架分層在Hibernate之上,所以我甚至不能在哪裏它實際上寫出來,所以我希望有一種方法來使這個觸發器工作。在那兒?

+2

僅在代碼中執行這樣的規則是一個壞主意 - 多個同時更新很難處理。如果你在你的代碼中同步,你最終可能會在該鎖和數據庫鎖之間造成骯髒的死鎖。 – 2008-12-17 21:27:03

+0

底線 - Oracle觸發器很糟糕。除了更新序列值或「updated_by」類型字段之外,避免它們像鼠疫一樣。他們的觸發器在90年代吸入,現在​​吸入。 – 2017-04-12 02:08:42

回答

7

我想我不同意你的觸發器試圖以 做什麼的描述。在我看來,它是爲了強制執行此業務規則:對於給定值爲t1_appnt_event的 ,一次只有一行非空值爲 t1_prnt_t1_pk。 (不管它們在第二列中的值是否相同)

有趣的是,它是爲t1_appnt_event的UPDATE定義的,但不是用於其他列的,所以我認爲有人可以通過更新來破壞規則第二列,除非該列有單獨的觸發器。

可能有一種方法可以創建一個強制執行此規則的基於函數的索引,以便完全擺脫觸發器。我想出了一個辦法,但它需要一些假設:

  • 該表具有數字主鍵
  • 主鍵和t1_prnt_t1_pk都總是正數

如果這些假設是真的,您可以創建這樣一個功能:

dev> create or replace function f(a number, b number) return number deterministic as 
    2 begin 
    3 if a is null then return 0-b; else return a; end if; 
    4 end; 

和索引這樣的:

CREATE UNIQUE INDEX my_index ON my_table 
    (t1_appnt_event, f(t1_prnt_t1_pk, primary_key_column)); 

因此,PMNT列爲NULL的行將出現在索引中,並且主鍵的相反值作爲第二個值,因此它們永遠不會相互衝突。行不是NULL的行將使用該列的實際(正)值。如果兩行在兩列中都具有相同的非空值,則可能會遇到違反約束條件的唯一方法。

這可能過於「聰明」,但它可能有助於解決您的問題。

從保羅湯布林更新:我與更新原來的想法,伊戈爾放在意見去:

CREATE UNIQUE INDEX cappec_ccip_uniq_idx 
ON tbl1 (t1_appnt_event, 
    CASE WHEN t1_prnt_t1_pk IS NOT NULL THEN 1 ELSE t1_pk END); 
+0

哦,好的。你有我的希望了一會兒。:-) – 2008-12-17 21:23:49

0

我同意戴夫,理想的結果probalby可以而且應該實現使用內置約束如唯一索引(或唯一約束)。

如果您確實需要解決變異表錯誤,通常的方法是創建一個包,其中包含一個包變量表,該變量表可以用來標識更改的行(我認爲ROWID是可能的,否則你必須使用PK,我目前不使用Oracle,所以我無法測試它)。然後FOR EACH ROW觸發器將所有由語句修改的行填充到這個變量中,然後有一個AFTER每個語句觸發器讀取行並驗證它們。

喜歡的東西(語法可能是錯的,我還沒有與Oracle工作了數年)

CREATE OR REPLACE PACKAGE trigger_pkg; 
    PROCEDURE before_stmt_trigger; 
    PROCEDURE for_each_row_trigger(row IN ROWID); 
    PROCEDURE after_stmt_trigger; 
END trigger_pkg; 

CREATE OR REPLACE PACKAGE BODY trigger_pkg AS 
    TYPE rowid_tbl IS TABLE OF(ROWID); 
    modified_rows rowid_tbl; 

    PROCEDURE before_stmt_trigger IS 
    BEGIN 
     modified_rows := rowid_tbl(); 
    END before_each_stmt_trigger; 

    PROCEDURE for_each_row_trigger(row IN ROWID) IS 
    BEGIN 
     modified_rows(modified_rows.COUNT) = row; 
    END for_each_row_trigger; 

    PROCEDURE after_stmt_trigger IS 
    BEGIN 
     FOR i IN 1 .. modified_rows.COUNT LOOP 
     SELECT ... INTO ... FROM the_table WHERE rowid = modified_rows(i); 
     -- do whatever you want to 
     END LOOP; 
    END after_each_stmt_trigger; 
END trigger_pkg; 

CREATE OR REPLACE TRIGGER before_stmt_trigger BEFORE INSERT OR UPDATE ON mytable AS 
BEGIN 
    trigger_pkg.before_stmt_trigger; 
END; 

CREATE OR REPLACE TRIGGER after_stmt_trigger AFTER INSERT OR UPDATE ON mytable AS 
BEGIN 
    trigger_pkg.after_stmt_trigger; 
END; 

CREATE OR REPLACE TRIGGER for_each_row_trigger 
BEFORE INSERT OR UPDATE ON mytable 
WHEN (new.mycolumn IS NOT NULL) AS 
BEGIN 
    trigger_pkg.for_each_row_trigger(:new.rowid); 
END; 
0

不同,需要 放於任何觸發型(或應用基於代碼的)解決方案鎖定以防止多用戶環境中的數據損壞。 即使您的觸發器工作或被重寫以避免突變表 問題,它也不會阻止2個用戶同時更新 t1_appnt_evnt_id到t1_appnt_evnt_id不是 的行上的相同值null:假設沒有行其中t1_appnt_evnt_id = 123 t1_prnt_t1_pk不爲空:

Session 1> update tbl1 
      set t1_appnt_evnt_id=123 
      where t1_prnt_t1_pk =456; 
      /* OK, trigger sees count of 0 */ 

Session 2> update tbl1 
      set t1_appnt_evnt_id=123 
      where t1_prnt_t1_pk =789; 
      /* OK, trigger sees count of 0 because 
       session 1 hasn't committed yet */ 

Session 1> commit; 

Session 2> commit; 

您現在有一個損壞的數據庫!

避免這種情況(在觸發器或應用程序代碼)的方法是在執行檢查之前鎖定 由t1_appnt_evnt_id = 123所引用的表中的父行:

select appe_id 
into v_app_id 
from parent_table 
where appe_id = :new.t1_appnt_evnt_id 
for update;  

現在會議2的觸發必須等待讓會話1在執行檢查之前提交或回滾。

實現戴夫科斯塔的索引將會更簡單和更安全!

最後,我很高興沒有人建議將PRAGMA AUTONOMOUS_TRANSACTION添加到您的觸發器中:這經常在論壇上提出,並且在突變表問題消失的情況下工作 - 但會使數據完整性問題更加嚴重!所以只是不...

0

我有類似的錯誤與休眠。並通過使用沖洗會話

getHibernateTemplate().saveOrUpdate(o); 
getHibernateTemplate().flush(); 

爲我解決了這個問題。 (我沒有發佈我的代碼塊,因爲我確信一切都寫得很好,應該可以工作 - 但是直到我添加了前面的flush()語句)。也許這可以幫助某人。

相關問題