我想找到一个使用 postgres 和 PDO (php)实现作业队列的好方法。
基本上我有一个events
表,用于记录应用程序的事件,以及某种形式的预定处理器(比如说proc
),它会定期检索一次事件并且执行某些例程以响应它(并根据事件的性质而自行)。
显然,只要proc
的实例开始处理某个事件,我就需要将该行标记为ongoing
,如下所示:
UPDATE events SET status = "ongoing" WHERE id = 3; -- >> QUERY 1 <<
精细! proc
现在可以根据事件类型及其有效负载开展业务,而其他任何线程都不会处理id = 3的事件,因为它现在是ongoing
。
当proc
完成事件3时,它会将其标记为已解决&#39;所以,再一次,没有其他线程将来会照顾事件3.我们走了:
UPDATE events SET status = "resolved" WHERE id = 3; -- >> QUERY 2 <<
现在我担心这必须在交易中完成,所以我会:
BEGIN;
-- QUERY 1
-- VARIOUS OTHER QUERIES TAKING A LOT OF TIME
-- QUERY 2
COMMIT;
据我所知,在事务内部,QUERY 1操作的更改仅在提交整个事务时对其他线程可见。这意味着虽然proc
(实例1)正在进行耗时的工作(QUERY 1和QUERY 2之间的代码),但它的一些其他实例可能会读取events
表,并认为没有人正在照顾事件3并继续用它来做事。很明显,这会弄乱整个事情并破坏队列的状态。
所以我的问题是:如何保留proc
的交易风格,同时更改事件3的状态(来自free
从交易外部可以看到ongoing
)立即?
答案 0 :(得分:1)
如上所述,另一名试图声明作业的工作人员会在查询1处阻塞。它可以看到该行的旧版本,但无法更新它 - 它会阻止。
所以不要在一次交易中做到这一点。索赔和承诺;做的工作;然后解决并提交。任何工作人员都会看到该行已被声明。此外,您可以看到它已声明,这将有助于您进行调试和监控。
当你声明这一行时,你应该标记一些独特的东西(一个pid,如果只有一个工人机器,或者一个主机名和pid,如果有几个)而不是简单地使用&#39;正在进行的&#39;。这样,如果工人死亡,你可以手动清理它。
答案 1 :(得分:0)
根据定义,您无法在该交易之外看到交易中所做的更改。
Transactions是所有数据库系统的基本概念。事务的关键点在于它将多个步骤捆绑为单个,全有或全无操作。其他并发事务对这些步骤之间的中间状态不可见,如果发生某些阻止事务完成的故障,则根本没有任何步骤影响数据库。
对于并发问题,我建议您使用serializable transaction isolation level和/或row-level locking。
答案 2 :(得分:0)
如图所示,这是不可能的。 PostgreSQL没有dirty reads,而QUERY1
没有意义,因为QUERY2
的效果会在被人看到之前被覆盖。
但是即使它立刻被提交并且可见(如果是独立承诺的话),这无论如何都不会令人满意。在高并发环境中,队列中SELECT行的SELECT和其{UP}状态为ongoing
之间的时间足以让另一个工作者也选择它并创建您想要避免的混淆。
我认为,如果您的QUERY1
替换为队列ID上的advisory lock,则可以通过替代您的设计来实现。
的伪代码:
BEGIN;
SELECT pg_try_advisory_xact_lock(3) INTO result;
IF result=true THEN
-- grabbed the exclusive right to process this entry
-- recheck the status now that the lock is taken
SELECT status INTO var_status FROM events WHERE id=3;
IF var_status='needs-to-be-done' THEN
-- do the work...
-- work is done
UPDATE events SET status = 'resolved' WHERE id = 3;
END IF;
ELSE
-- nothing to do, another worker is on it
END IF;
COMMIT;
这种锁在交易结束时自动释放。 与SELECT后跟UPDATE相反,保证锁定以原子方式被授予或拒绝。
答案 3 :(得分:0)
Postgres 确实有一个简单的方法来实现脏读:
查询 1:读取第一条可用记录并将其标记为 ongoing
UPDATE events SET status='ongoing', status_updated_at=clock_timestamp() WHERE id IN (
SELECT id FROM events
WHERE status!='ongoing'
AND status!='resolved'
ORDER BY id LIMIT 10
FOR UPDATE SKIP LOCKED
) RETURNING *;
在不锁定这些记录或整个表的代码中处理这些事件。如果它们是事件,这里假设它们无论如何都是不可变的,所以没有人应该更新它们。
查询 2:一旦处理,将这些事件标记为 resolved
。
UPDATE events SET status='resolved', status_updated_at=clock_timestamp()
WHERE id IN (<eventIds captured from above>)
如果我在这里遗漏了什么,请告诉我?