[英]PostgreSQL select for update lock, new rows
我有以下並發用例:可以隨時調用端點並且應該發生操作。 操作在偽代碼中是這樣的(當前隔離級別是READ COMMITTED
):
SELECT * FROM TABLE_A WHERE IS_LATEST=true FOR UPDATE
// DO SOME APP LOGIC TO TEST VALIDITY
// ALL GOES WELL => INSERT OR UPDATE NEW ROW WITH IS_LATEST=TRUE => COMMIT
// OTHERWISE => ROLLBACK (all good not interesting)
現在,如果這些操作中的兩個在更新方面同時開始,則使用SELECT FOR UPDATE
的方法很好。 因為兩個事務看到相同的行數,一個將更新行,第二個事務將等待輪到它才能進行SELECT FOR UPDATE
並且 state 有效。
我遇到的問題是當我在第一筆交易中插入時。 發生的情況是,例如,當第一個事務鎖定SELECT FOR UPDATE
時,有兩行,然后事務繼續,在事務中間,第二個事務進來想要SELECT FOR UPDATE
(最新)並等待第一個transaction to finish.. 第一個事務完成並且數據庫中實際上有一個新的第三個項目,但是第二個事務在等待行鎖被釋放時只拾取了兩行。 (這是因為在調用SELECT FOR UPDATE
時,快照不同,只有兩行匹配IS_LATEST=true
)。
有沒有辦法讓SELECT
鎖在等待后獲取最新的快照?
使用您的方法,第二個事務中的查詢將在鎖定消失后返回空結果,因為它在相關行上看到is_latest = FALSE
,並且新行尚不可見。 所以在這種情況下你必須重試交易。
我建議您改用REPEATABLE READ
隔離級別和樂觀鎖定:
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM table_a WHERE is_latest; -- no locks!
/* perform your application ruminations */
UPDATE table_a SET is_latest = FALSE WHERE id = <id you found above>;
INSERT INTO table_a (is_latest, ...) VALUES (TRUE, ...);
COMMIT;
然后可能會發生三件事:
您的查詢找到一行,事務成功。
您的查詢沒有找到任何行,那么您可以插入第一行。
查詢找到一行,但更新該行會導致序列化錯誤。
在那種情況下,您知道並發事務受到干擾,並且您重復完整的事務作為響應。
問題是每個命令只能看到在查詢開始之前已經提交的行。 有各種可能的解決方案......
您可以使用更嚴格的隔離級別來解決這個問題,但這樣做的成本相對較高。
保持(便宜的)默認隔離級別READ COMMITTED
,然后開始一個新命令。
雖然只鎖定了滿滿一手行,但最簡單的解決方案是重復相同的SELECT... FOR UPDATE
。 第二次迭代看到新提交的行並額外鎖定它們。
有一個理論上的競爭條件,附加事務可能會在等待事務之前鎖定新行。 這將導致僵局。 極不可能,但絕對可以肯定的是,以一致的順序鎖定行:
BEGIN; -- default READ COMMITTED
SELECT FROM table_a WHERE is_latest ORDER BY id FOR UPDATE; -- consistent order
SELECT * FROM table_a WHERE is_latest ORDER BY id FOR UPDATE; -- just repeat !!
-- DO SOME APP LOGIC TO TEST VALIDITY
-- pseudo-code
IF all_good
UPDATE table_a SET is_latest = true WHERE ...;
INSERT table_a (IS_LATEST, ...) VALUES (true, ...);
COMMIT;
ELSE
ROLLBACK;
END;
(id) WHERE is_latest
上的部分索引將是理想的。
對於超過一手的行,我會改為創建一個專用的單行標記表。 防彈實現可能如下所示,以管理員或超級用戶身份運行:
CREATE TABLE public.single_task_x (just_me bool CHECK (just_me) PRIMARY KEY DEFAULT true);
INSERT INTO public.single_task_x VALUES (true);
REVOKE ALL ON public.single_task_x FROM public;
GRANT SELECT, UPDATE ON public.single_task_x TO public; -- or just to those who need it
看:
然后:
BEGIN; -- default READ COMMITTED
SELECT FROM public.single_task_x FOR UPDATE;
SELECT * FROM table_a WHERE is_latest; -- FOR UPDATE? ①
-- DO SOME APP LOGIC TO TEST VALIDITY
-- pseudo-code
IF all_good
ROLLBACK;
ELSE
UPDATE table_a SET is_latest = true WHERE ...;
INSERT table_a (IS_LATEST, ...) VALUES (true, ...);
COMMIT;
END;
單鎖更便宜。
① 您可能想要也可能不想額外鎖定,以防御其他寫入,可能使用較弱的鎖定....
無論哪種方式,所有鎖都會在事務結束時自動釋放。
或者使用咨詢鎖。 pg_advisory_xact_lock()
在交易期間持續存在:
BEGIN; -- default READ COMMITTED
SELECT pg_advisory_xact_lock(123);
SELECT * FROM table_a WHERE is_latest;
-- do stuff
COMMIT; -- or ROLLBACK;
確保為您的特定任務使用唯一的令牌。 在我的例子中是123
。 如果您有許多不同的任務,請考慮使用查找表。
要在不同的時間點(不是事務結束時)釋放鎖,請考慮使用pg_advisory_lock()
的會話級鎖。 然后您可以(並且必須)使用pg_advisory_unlock()
手動解鎖 - 或者關閉 session。
這兩個都等待鎖定的資源。 有替代函數返回false
而不是等待......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.