【发布时间】:2011-09-24 08:22:10
【问题描述】:
我有一个典型的生产者-消费者问题:
多个生产者应用程序将作业请求写入 PostgreSQL 数据库上的作业表。
作业请求的状态字段在创建时开始包含 QUEUED。
当生产者插入新记录时,规则会通知多个消费者应用程序:
CREATE OR REPLACE RULE "jobrecord.added" AS
ON INSERT TO jobrecord DO
NOTIFY "jobrecordAdded";
他们将尝试通过将状态设置为 RESERVED 来保留新记录。当然,只有一个消费者应该成功。所有其他消费者不应该能够保留相同的记录。他们应该使用 state=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 Select unlocked row in Postgresql PostgreSQL and locking
从这些主题中,我了解到,以下 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 语句,使多个消费者不会保留相同的记录?
【问题讨论】:
-
你在交易中这样做,不是吗?有一个 BEGIN 和一个 COMMIT?
-
不,没有 BEGIN 和 COMMIT。我尝试在事务中执行此操作,但是什么也没发生,我的消费者不处理任何事情,表保持不变。我应该使用交易吗?
-
看来,事务的问题在于我在消费者应用程序(用 Qt 编写)中使用它的方式。
-
好的,在下面看到了您的答案,也许您可以使用最终的工作代码进行编辑,因为这是一个非常常见的问题
-
如果您在事务中执行此操作但什么都没有发生,我的猜测是您没有提交,因为在这种情况下 tx 会回滚。
标签: sql postgresql queue producer-consumer