多个消费者的工作队列做了两次相同的工作

时间:2017-05-11 09:01:38

标签: postgresql

实际上这里可能涉及很多事情: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 *

这实际上会有所帮助,只会发出一次相同的工作吗?

1 个答案:

答案 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将会更好地扩展。