簡體   English   中英

使用Oracle和PL / SQL插入或更新

[英]Insert or Update using Oracle and PL/SQL

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

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語句也不是原子的,可能會遇到類似的問題。

有誰知道如何防止這種情況發生?

正如Oracle告訴您的那樣,您遇到的情況並非不可能。 如果另一個進程插入了您要插入但尚未提交的密鑰,則可以得到描述的行為。 更新不會看到插入的記錄,但是即使插入的行尚未提交,也禁止嘗試將重復值添加到唯一索引中。

想到的唯一解決方案是最小化任何未提交的插入在該表上徘徊的時間,或者實施某種鎖定方案,或者在插入失敗時等待其他事務完成。

不太同意DCookie。

如果會話A插入值“ blue”(強制為唯一),然后會話B插入值“ blue”,則會話B將等待會話A的鎖。如果會話A提交,則會話B將獲得約束違反。 如果會話A進行回滾,則將允許會話B繼續。

會話A可能會有很小的范圍來插入行並提交,會話B會得到約束違例,然后在會話B更新之前刪除該行。 我認為那不太可能。

我首先來看一下target_total表上是否只有一個唯一約束。 如果不是,則要非常確定是哪個約束導致了違規。 還要檢查唯一索引和約束。

檢查是否存在任何數據類型不匹配或干擾觸發。 在選擇匹配中,NUMBER(2,0)可能不等於1.1數值,但在插入時1.1會被截斷為1.0,從而可能會引發約束沖突。 在我的示例中,如果觸發器強制使用大寫字母“ BLUE”,則選擇可能無法在“ blue”上匹配,插入可能會在“ BLUE”上的重復鍵上失敗,並且隨后的插入也會在“ blue”上匹配失敗藍色”。

然后檢查變量命名。 在INSERT .... VALUES( 標識符 )中, 標識符必須是PL / SQL變量。 但是,SELECT * FROM table WHERE column = identifier ,則標識符可能是列名,而不是PL / SQL變量。 如果存在列名或accountId函數,則該列名將優先於同名的PL / SQL變量。 給PL / SQL變量加上前綴是一個好習慣,以確保永遠不會出現這樣的名稱空間沖突。

我唯一的另一個想法是,由於您正在運行多線程,因此線程是否有可能發生沖突。 在實時環境中,線程可能會碰到其他會話的鎖時,這種可能性更大。 這可能會迫使他們以一種奇怪的方式進行同步,而這種方式在測試中沒有出現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM