丢包更新是否可以在PostgreSQL中的读提交隔离级别中发生?

时间:2013-01-25 04:53:01

标签: postgresql concurrency worker-process

我在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的信息并处理工作,但有时他们会抓取相同的工作并处理两次或更多次。隔离级别为Read Committed。

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

  • 我尝试了SELECT FOR UPDATE,但它导致死锁情况。
  • 我尝试了pg_try_advisory_xact_lock,但它导致 out of shared memory
  • 我尝试将AND pg_try_advisory_xact_lock(queue.id)添加到外部查询的WHERE子句中,但是...... [?]

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:6)

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

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

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

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

你不能用咨询锁来解决这个问题,不管怎么说都不容易。使用非阻塞锁定尝试迭代队列的黑客,直到你得到第一个可锁定的项目,但是会很慢而且很笨拙。

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

相关:

答案 1 :(得分:0)

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