Postgresql FOR UPDATE SKIP LOCKED仍然选择重复的行

时间:2016-04-21 05:44:13

标签: multithreading postgresql psycopg2

我使用PostgreSQL作为作业队列。以下是我检索作业并更新其状态的查询:

        UPDATE requests AS re
        SET
          started_at = NOW(),
          finished_at = NULL
        FROM (
          SELECT
            _re.*
          FROM requests AS _re
          WHERE
            _re.state = 'pending'
          AND
            _re.started_at IS NULL
          LIMIT 1
          FOR UPDATE SKIP LOCKED
        ) AS sub
        WHERE re.id = sub.id
        RETURNING
          sub.*

现在,我有几台机器,在每台机器上我有一个带有多个线程的进程,并且在每个线程上我都有一个worker。同一进程中的所有工作程序共享一个连接池,通常具有10-20个连接。

问题是,上面的查询会多次返回一些行!

我找不到任何理由。有人可以帮忙吗?

更详细一点,我使用的是Python3和psycopg2。

更新

我试过@ a_horse_with_no_name的回答,但似乎行不通。

我注意到,一个请求被两个查询检索,started_at更新为:

  

2016-04-21 14:23:06.970897 + 08

  

2016-04-21 14:23:06.831345 + 08

仅相差0.14s。

我想知道当这两个连接执行内部SELECT子查询时,两个锁都没有建立吗?

更新

更确切地说,我在一台机器上的一个过程中有200名工人(即200个线程)。

2 个答案:

答案 0 :(得分:2)

请注意,如果您不希望他们互相介入,每个帖子都必须拥有自己的连接。

  

如果您的应用程序使用多个执行线程,则不能   同时共享连接。您必须明确控制   访问连接(使用互斥锁)或为每个连接使用连接   线。如果每个线程都使用自己的连接,则需要使用   AT子句指定线程将使用哪个连接。

来自:http://www.postgresql.org/docs/9.5/static/ecpg-connect.html

如果两个线程共享相同的连接,则会发生各种奇怪的事情。我相信这就是你的情况。如果使用一个连接进行锁定,则使用相同连接的所有其他线程将可以访问锁定的对象。

请允许我建议一种替代方法,这非常简单。使用redis作为队列。您可以简单地使用redis-py和lpush / rpop方法,也可以使用python-rq。

答案 1 :(得分:1)

有可能在select时发出锁定事务,或者在select的结果准备好并且update语句开始时锁定丢失。您是否尝试过明确开始交易?

BEGIN;
  WITH req AS (
    SELECT id
    FROM requests AS _re
    WHERE _re.state = 'pending' AND _re.started_at IS NULL
    LIMIT 1 FOR UPDATE SKIP LOCKED
    )
  UPDATE requests SET started_at = NOW(), finished_at = NULL
  FROM req
  WHERE requests.id = req.id;
COMMIT;