2017-03-21 37 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; 

一个复杂的查询开始处理,这是不是很令人满意。