2011-10-27 40 views
1

也許這是我的天真,也許是我的偏執狂,但我認爲我正在尋找一種解決方案來解決種族問題,看起來應該如此普遍,會有大量解決方案,我會找到一個現在......但我沒有。意味着更新在哪裏值是子查詢有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,自動解鎖,也可以不明確地解鎖
  • 我沒有測試過這個!我將編輯/更新當我有...
+0

您是否想過在兩個SQL會話中測試這個? –

+1

小問題:除非trans_nbr有時爲NULL,否則用'COUNT(*)'與'COUNT(trans_nbr)'可能會得到更好的結果,因爲前者我相信可以更好地優化。 – Flimzy

+0

@ MikeSherrill'Catcall':是的,我想過了。開始處理一些測試數據/查詢,但被調用到其他任務。 (我現在正在重新審視這個問題,因爲有人提出了一個解決方案。:-) 1.從邏輯上講,我提到的問題是「理論上可行」,問題是否存在。即使進行了測試,這些併發問題也難以複製。所以,「實際看到它發生」並不總是可能的。並沒有詳細的知識來源...(是的,FLOSS,但是這種類型的代碼是很難[我; - ]通過......特別是當我沒有處理它時24/7 – pythonlarry

回答

1

如何鎖定一個額外的子查詢層?

UPDATE my_table 
     SET processing_by = our_id_info -- unique to this instance 
    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(trans_nbr) > 1 
            LIMIT our_limit_to_have_single_process_grab 
           ) 
         FOR UPDATE 
         ) 
RETURNING row_id