实际上这里可能涉及很多事情:Job queue as SQL table with multiple consumers (PostgreSQL)
但是我只是想问一下我的具体问题。
目前我有一个工作队列实际上应该为每个消费者发放一份新工作,但是我们发现我们有时会在不同的消费者身上两次获得相同的工作(可能是竞争条件)。 这是我们的查询(在事务中运行):
UPDATE invoice_job SET status = 'working', date_time_start = now(),
node = $ip
WHERE id = (SELECT id FROM invoice_job WHERE status = 'created' ORDER BY id LIMIT 1)
RETURNING *
目前,表格非常简单且具有状态(可以是"创建","工作","完成",date_time_start字段,创建字段(不是用于查询),id字段,节点(运行作业的地方)。
然而,这在一个点上两次发出相同的作业。 目前我现在将查询更改为:
UPDATE invoice_job SET status = 'working', date_time_start = now(),
node = $ip
WHERE id = (SELECT id FROM invoice_job WHERE status = 'created' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED)
RETURNING *
这实际上会有所帮助,只会发出一次相同的工作吗?
答案 0 :(得分:1)
FOR UPDATE SKIP LOCKED
的解决方案很好。在更新进行处理之前,它将确保一行仅被一个会话锁定。没有事务可以选择已被另一个事务锁定的行,并且在提交时释放锁定时,后续SELECT
子句将不再与该行匹配。
原始版本失败,因为子查询的SELECT
可以在多个会话中同时选择同一行,然后每个会话尝试UPDATE
该行。 WHERE
中没有UPDATE
条款导致失败;对UPDATE invoice_job SET status = 'working' WHERE node = 42
或其他两个并发会话来说,它是完全正常的。第一次更新成功后,第二次更新将很乐意运行并提交。
您还可以通过重复WHERE
UPDATE
条款来确保安全
UPDATE invoice_job SET status = 'working', date_time_start = now(),
node = $ip
WHERE id = (SELECT id FROM invoice_job WHERE status = 'created' ORDER BY id LIMIT 1)
AND status = 'created'
RETURNING *
...但这通常会在高并发性下返回零行。
实际上除了一组并发执行之外,它将返回零行,因此它不比串行队列工作者好。对于人们用来尝试进行并发队列的大多数其他“聪明”技巧都是如此,这也是引入SKIP LOCKED
的主要原因之一。
您现在只注意到这个问题的事实告诉我,在选择第一行之前,您LOCK TABLE
进行简单的串行队列调度实际上很好。但是,如果您的工作量增加,SKIP LOCKED
将会更好地扩展。