也許這是我的天真,也許是我的偏執狂,但我認爲我正在尋找一種解決方案來解決種族問題,看起來應該如此普遍,會有大量解決方案,我會找到一個現在......但我沒有。意味着更新在哪裏值是子查詢有GROUP BY所以沒有競爭條件問題?
簡單的情況是我有一個過程,應該抓住任何記錄,其中有一個以上的某種類型。我想讓系統/進程(thread)/線程/多處理/重入/安全的流行語;如果同一過程開始並引入試圖抓住感興趣的行的競爭條件,我希望有明確的贏家/輸家:一個成功,另一個錯誤;實際上,我更喜歡第二個無縫的,沉默的,優雅的「失敗」,因爲它只是不會看到第一次被抓住的那些東西。
因此我的困境。
查詢我已經是這樣的:
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
WHERE trans_nbr IN (
SELECT trans_nbr
FROM my_table
GROUP BY trans_nbr
HAVING COUNT(trans_nbr) > 1
LIMIT our_limit_to_have_single_process_grab
)
RETURNING row_id
我的想法是:我想,有沒有鎖,所以沒有子查詢和外部之間更新「狀態」的保證。那麼,如何確保任何候選人這個進程得到,我們抓取,並且他們還沒有被另一個進程同時抓住?
我想過在子查詢的末尾添加一個「FOR UPDATE ON my_table」,但那不行;不能有這個和一個「GROUP BY」(這是計算trans_nbr的計數所必需的)。 (因爲這會迫使我們的更新阻止任何轉發,所以這將是一個首選的解決方案,因爲這樣既避免了競爭條件導致的錯誤[兩個進程抓取同一行{s}],並允許那些其他進程是幸福地沒有意識,只是得到不再包含那些第一個進程抓取的行)唉
我想過鎖定表,但是(在Postgres中,至少)表鎖只能釋放在COMMIT之後;出於測試目的,我不想COMMIT,因此在測試過程中(是的,在測試數據庫測試後的實時數據庫上的預先測試),它不會去這條路線。 (另外,即使是現場直播,如果有足夠的用戶/進程,這將給予不可接受的性能。)
我想過要根據processing_by值爲我們的子查詢進行更新,但同樣, t工作:如果在子查詢中,會打破GROUP BY/HAVING條件(因爲現在會有被計數的trans_nbr/processing_by的子組,這不是我所追求的)。我很期待在正確的方向上嘲笑我,問我這樣一個明顯的問題,但這對我來說並不明顯(顯然; o),我向你保證,我一直在研究這幾個小時。
非常感謝您的提示,更不用說解決方案了!
UPDATE:感謝這麼多Chris Travers!
回想起那條「Forrest for the Trees」的線條! :>
下面是查詢的修改版本,將此建議考慮在內,並添加了另一個「雙重檢查」。這應該是唯一。
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
WHERE trans_nbr IN (
SELECT trans_nbr
FROM my_table
WHERE trans_nbr IN (
SELECT trans_nbr
FROM my_table
GROUP BY trans_nbr
HAVING COUNT(*) > 1 -- Thanks for the suggestion, Flimzy
LIMIT our_limit_to_have_single_process_grab
)
AND processing_by IS NULL
/* Or some other logic that says "not currently being
processed". This way, we ALSO verify we're not
grabbing one that might have been UPDATEd/grabbed
during our sub-SELECT, while it was being
blocked/waiting.
This COULD go in our UPDATE/top-level, but unnecessary
rows could be locked by this lower-level in that case.
*/
FOR UPDATE /* Will block/wait for rows this finds to be unlocked by
any prior transaction that had a lock on them.
NOTE: Which _could_ allow the prior trans to change
our desired rows in the mean time, thus the
secondary WHERE clause.
*/
)
RETURNING row_id
我很喜歡Postgres擁有類似SKIP LOCKED的功能。特別是對於基本原子行的隊列,需要處理而不會阻塞其他處理。 But alas.Maybe someday...?Or "soon"? :-)
現在,人們可以添加NOWAIT拿不到阻止任何其他交易(一個或多個),但要記住,它只是轉儲回來了一個錯誤 - 你必須要不斷嘗試你查詢直到成功(或放棄)。如果沒有NOWAIT,查詢會阻塞,直到其他事務釋放其鎖,或查詢超時。
更新2: SO,之後重新再重新閱讀「爲樹阿甘」的時刻這一點,並思考它,再次。我可以簡單地這樣做:
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
WHERE trans_nbr IN (
-- This query MAY pull ones we don't want to mess with (already "grabbed")
SELECT trans_nbr
FROM my_table
GROUP BY trans_nbr
HAVING COUNT(*) > 1
LIMIT our_limit_to_have_single_process_grab
AND processing_by IS NULL -- only "ungrabbed" ones (at this point)
)
AND processing_by IS NULL -- But THIS will drop out any "bogus" ones that changed between subquery and here
RETURNING row_id
COMMIT交易釋放我們的鎖,和鮑勃的叔叔。儘管如此,SKIP LOCKED仍然是超級酷炫的。
A CAVEATE:如果有人要讓工人拉一個有限的(如LIMIT 1)行數和/或物品必須按特定順序抓取(例如:FIFO,ORDER BY和/或按功能例如Min(id)),可能會出現飢餓工人的情況:工人等待並等待,以及他們等待解鎖的行時,結果都沒有達到最終標準。有很多方法可以嘗試解決這個問題,比如讓工作人員通過OFFSET跳過,但大多數都是複雜或緩慢的。 (!通常都BONUS)
MY functionailty預計多行返回,或沒有都OK - 什麼現在做的;睡了一下,並重新檢查,所以這對我來說不是問題。它可能適合你。如果是這樣,你要考慮...
非阻塞版本:我發現了一個great article這個問題非常的工作,事實證明,它把我介紹給PG的Advisory Locks。 (This one是相當有價值的信息,太多。)
所以,無阻塞解決我自己的問題應該是這樣的:
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
WHERE trans_nbr IN (
-- This query MAY pull ones we don't want to mess with (already "grabbed")
SELECT trans_nbr
FROM my_table AS inner_my_table_1
GROUP BY trans_nbr
HAVING Count(*) > 1
AND Count(*) in (-- For MY query, since I'm grouping-by, I want "all or none" of trans_nbr rows
SELECT Count(*)
FROM my_table AS inner_my_table_2
WHERE inner_my_table_2.trans_nbr = inner_my_table_1.trans_nbr
AND pg_try_advisory_xact_lock(id) -- INT that will uniquely ID this row
)
/* Note also that this will still lock all non-locked rows with this
trans_nbr, even though we won't use them unless we can grab ALL of the
rows with same trans_nbr... the rest of our query should be made
quick-enough to accept this reality and not tie up the server unduly.
See linked info for more-simple queries not doing group-by's.
*/
LIMIT our_limit_to_have_single_process_grab
AND processing_by IS NULL -- only "ungrabbed" ones (at this point)
)
AND processing_by IS NULL -- But THIS will drop out any "bogus" ones that changed between subquery and here
RETURNING row_id
注:
- 這是達應用程序做/尊重諮詢鎖,所以這不是安慰劑,但也不是安慰劑。同樣,SKIP LOCKED會因此而非常方便。
- pg_try_advisory_lock,因爲V 8.2,沒有自動解鎖,(因此)可以(MUST)被顯式解鎖
- pg_try_advisory_xact_lock,由於在交易的結尾處的v 9.1,自動解鎖,也可以不明確地解鎖
- 我沒有測試過這個!我將編輯/更新當我有...
您是否想過在兩個SQL會話中測試這個? –
小問題:除非trans_nbr有時爲NULL,否則用'COUNT(*)'與'COUNT(trans_nbr)'可能會得到更好的結果,因爲前者我相信可以更好地優化。 – Flimzy
@ MikeSherrill'Catcall':是的,我想過了。開始處理一些測試數據/查詢,但被調用到其他任務。 (我現在正在重新審視這個問題,因爲有人提出了一個解決方案。:-) 1.從邏輯上講,我提到的問題是「理論上可行」,問題是否存在。即使進行了測試,這些併發問題也難以複製。所以,「實際看到它發生」並不總是可能的。並沒有詳細的知識來源...(是的,FLOSS,但是這種類型的代碼是很難[我; - ]通過......特別是當我沒有處理它時24/7 – pythonlarry