2011-03-08 17 views
4

我有一個PL/SQL函數在Oracle數據庫上執行更新/插入操作,該數據庫維持目標總數並返回現有值和新值之間的差異。
這裏是我到目前爲止的代碼:使用Oracle和PL/SQL插入或更新

FUNCTION calcTargetTotal(accountId varchar2, newTotal numeric) RETURN number is 
oldTotal numeric(20,6); 
difference numeric(20,6); 

begin 
    difference := 0; 
    begin 
     select value into oldTotal 
     from target_total 
     WHERE account_id = accountId 
     for update of value; 

     if (oldTotal != newTotal) then 
      update target_total 
      set value = newTotal 
      WHERE account_id = accountId 
      difference := newTotal - oldTotal; 
     end if; 
    exception 
     when NO_DATA_FOUND then 
     begin 
      difference := newTotal; 
      insert into target_total 
       (account_id, value) 
      values 
       (accountId, newTotal); 

     -- sometimes a race condition occurs and this stmt fails 
     -- in those cases try to update again 
     exception 
      when DUP_VAL_ON_INDEX then 
      begin 
       difference := 0; 
       select value into oldTotal 
       from target_total 
       WHERE account_id = accountId 
       for update of value; 

       if (oldTotal != newTotal) then 
        update target_total 
        set value = newTotal 
        WHERE account_id = accountId 
        difference := newTotal - oldTotal; 
       end if; 
      end; 
     end; 
    end; 
    return difference 
end calcTargetTotal; 

這將按預期在單元測試中多線程永不失敗。
但是我們已經看到了正在運行的系統上加載時,這個失敗,堆棧跟蹤看起來像這樣:

ORA-01403: no data found 
ORA-00001: unique constraint() violated 
ORA-01403: no data found 

行號(我已經刪除,因爲它們是無意義的斷章取義)驗證第一個更新由於沒有數據而失敗,插入由於唯一性而失敗,並且第二次更新在沒有數據的情況下失敗,這是不可能的。

從我在其他線程上讀到的MERGE語句也不是原子的,可能會遇到類似的問題。

有沒有人有任何想法如何防止這種情況發生?

+0

是否只有一列(accountID)上的唯一索引?還是有沒有第二列,你沒有顯示,以簡化說明? – redcayuga 2011-03-09 16:39:15

+0

如何定義唯一約束?一個獨特的索引?具有唯一顯式約束的非唯一索引?如果定義了明確的唯一約束,它是否可延遲? – redcayuga 2011-03-09 16:47:28

回答

1

正如甲骨文告訴你的,這不是一個不可能的情況。如果另一個進程插入了您嘗試插入但尚未提交的密鑰,則可以獲取所描述的行爲。更新不會看到插入的記錄,但即使插入的行尚未提交,也會禁止嘗試將重複值添加到唯一索引。

想到的唯一解決方案是儘量減少任何未提交的插入對此表執行的時間量,或者實現某種鎖定方案,或者等待插入操作失敗以等待其他事務完成。

1

不太同意DCookie。如果會話A插入值「藍色」(強制爲唯一),然後會話B插入值「藍色」,則會話B將等待來自會話A的鎖定。如果會話A提交,則會話B將會違反約束。如果會話A執行回滾,則會話B將被允許繼續。

可能會有一個非常小的範圍讓會話A插入一行並提交它,會話B得到約束違例,然後在會話B更新之前刪除該行。儘管如此,我很難判斷。

我首先看看target_total表中是否只有一個唯一約束。如果不是,你想要確定哪些約束導致違規。還檢查唯一索引以及約束。

檢查是否有任何數據類型不匹配或干擾觸發。 NUMBER(2,0)在選擇匹配中可能不等於1.1數字值,但在插入時,1.1將被截斷爲1.0,可能會觸發違反約束條件。在我的示例中,如果觸發器強制使用大寫「BLUE」,則select可能無法在「blue」上匹配,插入可能會在「BLUE」上的重複鍵上失敗,並且後續插入也無法匹配「藍色」。

然後檢查變量命名。在INSERT .... VALUES(標識符)中,那麼標識符必須是PL/SQL變量。然而,SELECT * FROM表WHERE列= 標識符,然後標識符可能是列名稱而不是PL/SQL變量。如果列名或accountId的功能優先於同名的PL/SQL變量。爲PL/SQL變量加上前綴以確保永遠不會存在這樣的名稱空間衝突是一種好習慣。

我唯一的想法是,由於您正在運行多線程,線程是否有任何潛在的衝突。當線程可能從其他會話鎖定時,這可能更有可能在實時環境中。這可能會迫使他們以一種不會在測試中出現的奇怪方式進行同步。