2017-03-21 78 views
0

我遇到以下功能問題。這個函數的目的是返回一組記錄,如果在60秒內被調用,這些記錄將不會被再次返回(幾乎像隊列一樣)。Postgresql使用臨時表進行更新鎖定行

當我一次運行這個函數時它似乎工作正常,但是當我在我的線程應用程序中使用它時,我看到顯示的重複項。我是否正確鎖定行?插入臨時表時使用FOR UPDATE的正確方法是什麼?

CREATE OR REPLACE FUNCTION needs_quantities(computer TEXT) 
    RETURNS TABLE(id BIGINT, listing_id CHARACTER VARYING, asin CHARACTER VARYING, retry_count INT) 
LANGUAGE plpgsql 
AS $$ 

BEGIN 


    CREATE TEMP TABLE temp_needs_quantity ON COMMIT DROP 
    AS 

    SELECT 
     listing.id, 
     listing.listing_id, 
     listing.asin, 
     listing.retry_count 
    FROM listing 
    WHERE listing.id IN (
     SELECT min(listing.id) AS id 
     FROM listing 
     WHERE (listing.quantity_assigned_to IS NULL 

      --quantity is null 
      -- and quantity assigned date is at least 60 seconds ago 
      -- and quantity date is within 2 hours 

      OR (
       quantity IS NULL AND listing.quantity_assigned_date < now_utc() - INTERVAL '60 second' 
       AND (listing.quantity_date IS NULL OR listing.quantity_date > now_utc() - INTERVAL '2 hour') 
      ) 

      ) 
      AND listing.retry_count < 10 


     GROUP BY listing.asin 
     ORDER BY min(listing.retry_count), min(listing_date) 
     LIMIT 10 

    ) 
    FOR UPDATE; 


    UPDATE listing 
    SET quantity_assigned_date = now_utc(), quantity_assigned_to = computer 
    WHERE listing.id IN (SELECT temp_needs_quantity.id 
         FROM temp_needs_quantity); 

    RETURN QUERY 
    SELECT * 
    FROM temp_needs_quantity 
    ORDER BY id; 


END 
$$ 

回答

0

你的函數應該鎖定listing中的行,就像你在第一個線程中想要的一樣。

在第二線程中的問題是,此子選擇:

... 
WHERE listing.id IN (
    SELECT min(listing.id) AS id 
    FROM listing 
    ... 
    LIMIT 10 
) 

阻止這些行中的鎖,即使封閉SELECT ... FOR UPDATE是。

這樣子選擇會很樂意看到舊行版本之前的第一個線程的UPDATE,然後塊封閉SELECT ... FOR UPDATE直到第一個線程完成。然後它繼續再次更新相同的行。

我不確定這是否被認爲是一個錯誤–你可能想問在pgsql-general郵件列表。 最近CTE出現了類似問題,請參閱this commit message修復this bug。有人可能會說這是一個類似的情況。

不幸的是像你這樣在你面前,我想不出一個更好的解決方案,而不是

LOCK TABLE listing IN EXCLUSIVE MODE; 

一個複雜的查詢開始處理,這是不是很令人滿意。