我有一个典型的生产者-消费者问题:
多个生产者应用程序将作业请求写入PostgreSQL数据库上的作业表。
作业请求具有一个状态字段,在创建时包含QUEUED。
存在多个消费者应用程序,当生产者插入新记录时,它们会收到通知:
CREATE OR REPLACE RULE "jobrecord.added" AS
ON INSERT TO jobrecord DO
NOTIFY "jobrecordAdded";
他们将尝试通过将其状态设置为RESERVED来保留新记录。当然,只有一个消费者应该成功。所有其他消费者都不应能够预订相同的记录。他们应该预订状态为QUEUED的其他记录。示例: 某些生产者向表jobrecord添加了以下记录:
id state owner payload
------------------------
1 QUEUED null <data>
2 QUEUED null <data>
3 QUEUED null <data>
4 QUEUED null <data>
现在,有两个消费者A,B想要处理它们。他们同时开始运行。一个应该保留id 1,另一个应该保留id 2,然后完成得最快的那个应该保留id 3,以此类推。
在一个纯多线程的世界中,我会使用互斥锁来控制对作业队列的访问,但是这些消费者是不同的进程,可能在不同的机器上运行。他们只能访问同一数据库,因此所有同步必须通过数据库进行。
我阅读了很多关于PostgreSQL并发访问和锁定的文档,例如http://www.postgresql.org/docs/9.0/interactive/explicit-locking.html在Postgresql中选择未锁定的行PostgreSQL和锁定
从这些主题中,我学到了以下SQL语句应该能够满足我的需求:
UPDATE jobrecord
SET owner= :owner, state = :reserved
WHERE id = (
SELECT id from jobrecord WHERE state = :queued
ORDER BY id LIMIT 1
)
RETURNING id; // will only return an id when they reserved it successfully
很不幸,当我在多个消费者进程中运行此代码时,大约有50%的时间它们仍然会保留相同的记录,从而处理并覆盖另一个进程所做的更改。
我错过了什么?我该如何编写SQL语句才能使多个消费者不会保留相同的记录?