2013-02-18 95 views
3

我認爲如果一個進程從一個唯一的用戶標識中選擇餘額並嘗試進行插入,那麼餘額將被錯誤地更新,但是另一個進程在該情況發生之前讀取餘額。我該如何解決?如何在PostgreSQL存儲過程中查詢原子?

CREATE OR REPLACE FUNCTION incBalance(INTEGER, BIGINT) RETURNS void AS $$ 
DECLARE 
    balanceRecord record; 
    newBalance bigint; 
BEGIN 
    FOR balanceRecord IN  
     SELECT balance FROM users WHERE userid = $1 
    LOOP 
     newBalance := balanceRecord.balance + $2; 
     UPDATE users SET balance = newBalance WHERE userid = $1; 

    END LOOP; 
    RETURN; 
END; 
$$ LANGUAGE plpgsql; 

回答

7

對於這個特定的查詢,你可以重寫它是一個SQL語句:

UPDATE users SET balance = balance + $2 WHERE userid = $1; 

更一般地,你要讓交易系統處理的原子性和數據的一致性。在Postgres中,存儲過程總是在事務上下文中執行 - 如果您不是從明確的事務塊調用它,它會爲您創建一個。

http://www.postgresql.org/docs/9.2/static/sql-set-transaction.html討論瞭如果默認值不夠嚴格,如何設置隔離級別。

您需要閱讀http://www.postgresql.org/docs/9.2/static/mvcc.html以幫助確定適用於特定存儲過程的級別。注意第13.2.2和13.2.3節,其中警告說較高的隔離級別受到應該被捕獲的序列化異常的影響,並且事務被重試爲確保一致性的機制。

如果我有這樣一個過程,我在過程的第一個BEGIN塊的開頭添加一個語句,以確保事務處於足夠的隔離級別。如果沒有工作已經在交易中完成,它將在必要時提高它。如果調用上下文是已完成工作的事務,則如果封閉事務塊尚未充分提高隔離級別,則會導致錯誤。如果它已經高於您在此處指定的值,將會降低隔離級別而不是

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 
+0

如果我們說,同時執行數以千計的查詢,每個查詢都會爲該特定用戶設置「balance = balance + $ 2」,會發生什麼?難道在某個時候,「平衡」將會從較早的時間開始,增加/減少$ 2,失去一些中間增加/減少?爲什麼? – PF4Public 2015-12-23 14:10:19

+0

@ PF4Public使用[ACID](http://stackoverflow.com/questions/3740280/acid-and-database-transactions)兼容數據庫的重點在於避免出現此類問題。 ISOLATION LEVEL READ COMMITTED對於顯示的單個UPDATE語句已足夠。如提供的文檔鏈接中所討論的,更復雜的交易可能需要更高的級別。 – gwaigh 2015-12-23 15:08:52

+0

感謝您的回答和澄清 – PF4Public 2015-12-24 18:06:03