2

我在 PostgreSQL 中有如下查询:

UPDATE 
     queue 
SET 
  queue.status   = 'PROCESSING' 
WHERE 
    queue.status   = 'WAITING' AND
    queue.id       = (SELECT id FROM queue WHERE STATUS = 'WAITING' LIMIT 1 )
RETURNING 
        queue.id

并且许多工作人员尝试一次处理一项工作(这就是为什么我有限制为 1 的子查询)。在此更新之后,每个工作人员都会获取有关 id 的信息并处理工作,但有时他们会获取相同的工作并处理两次或更多次。隔离级别为已提交读。

我的问题是如何保证一项工作将被处理一次?我知道那里有很多帖子,但我可以说我已经尝试了其中的大部分但没有帮助 () ;

  • 我试过 SELECT FOR UPDATE,但它导致了死锁的情况。
  • 我试过pg_try_advisory_xact_lock,但它导致共享内存不足
  • 我尝试添加AND pg_try_advisory_xact_lock(queue.id)到外部查询的 WHERE 子句,但是... [?]

任何帮助,将不胜感激。

4

2 回答 2

6

在您描述的情况下不会发生丢失更新,但也不会正常工作。

在您上面给出的示例中会发生什么,假设(例如)10 个工作人员同时启动,所有 10 个工作人员都将执行子查询并获得相同的 ID。他们都将尝试锁定该 ID。其中一个会成功;其他人将阻止第一个锁。一旦第一个后端提交或回滚,其他 9 个后端将争夺锁。一会得到它,重新检查 WHERE 子句,发现queue.status测试不再匹配,然后不修改任何行就返回。其他 8 个也会发生同样的情况。因此,您使用 10 个查询来完成一个查询的工作。

如果您未能明确检查UPDATE结果并看到零行已更新,您可能会认为您丢失了更新,但事实并非如此。您的应用程序中只是由于对执行顺序和隔离规则的误解而导致了并发错误。真正发生的事情是,您正在有效地序列化您的后端,以便一次只有一个后端真正取得进展。

PostgreSQL 可以避免让它们都获得相同的队列项 ID 的唯一方法是对它们进行序列化,因此在查询 #1 完成之前它不会开始执行查询 #2。如果你愿意,你可以通过LOCKing 队列表来做到这一点......但同样,你也可以只拥有一个工人。

你不能用咨询锁来解决这个问题,无论如何都不容易。使用非阻塞锁定尝试迭代队列直到获得第一个可锁定项目的黑客行为会起作用,但会很慢而且很笨拙。

您正在尝试使用 RDBMS 实现工作队列。这不会很好地工作。它会很慢,会很痛苦,而且既正确又快速将非常非常困难。不要自己滚。相反,使用完善的、经过良好测试的系统来进行可靠的任务排队。看看 RabbitMQ、ZeroMQ、Apache ActiveMQ、Celery 等。还有来自 Skytools 的 PGQ,一个基于 PostgreSQL 的解决方案。

有关的:

于 2013-01-25T06:19:00.193 回答
0

SKIP LOCKED可用于在 PostgreSql 中实现队列。

于 2021-06-16T08:28:50.160 回答