我正在构建某种排队机制。有几行数据需要处理,还有一个状态标志。我正在使用 update .. returning
子句来管理它:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id from STUFF WHERE computed IS NULL LIMIT 1)
RETURNING *
嵌套的选择部分是否与更新相同的锁,或者我在这里有一个竞争条件?如果是这样,内部选择是否需要是 select for update
?
最佳答案
虽然 Erwin 的建议可能是获得正确行为的最简单方法(只要您在 SQLSTATE
为 40001 时重试事务),排队应用程序就其本质而言,与 SERIALIZABLE
事务的 PostgreSQL 实现相比,它们倾向于更好地处理请求阻塞以获得轮到队列的机会,后者允许更高的并发性并且对机会更“乐观”碰撞。
问题中的示例查询,就目前而言,在默认的 READ COMMITTED
事务隔离级别中,将允许两个(或更多)并发连接从队列中“声明”同一行。会发生什么:
- T1 开始并在
UPDATE
阶段锁定行。 - T2 在执行时间上与 T1 重叠并尝试更新该行。它阻止等待 T1 的
COMMIT
或ROLLBACK
。 - T1 提交,已成功“声明”该行。
- T2 尝试更新该行,发现 T1 已经有,寻找该行的新版本,发现它仍然满足选择条件(即
id
匹配),并且也“声明”该行。
可以对其进行修改以使其正常工作(如果您使用的 PostgreSQL 版本允许在子查询中使用 FOR UPDATE
子句)。只需将 FOR UPDATE
添加到选择 id 的子查询的末尾,就会发生这种情况:
- T1 启动并在选择 id 之前锁定行。
- T2 在执行时间上与 T1 重叠,并在尝试选择 id 时阻塞,等待 T1 的
COMMIT
或ROLLBACK
。 - T1 提交,已成功“声明”该行。
- 当 T2 能够读取该行以查看 id 时,它发现它已被声明,因此它会找到下一个可用的 id。
在 REPEATABLE READ
或 SERIALIZABLE
事务隔离级别,写冲突会抛出错误,您可以根据 SQLSTATE 捕获并确定是序列化失败,并重试。
如果您通常需要 SERIALIZABLE 事务,但又想避免在排队区重试,您可以使用 advisory lock 来实现。 .
关于multithreading - 原子更新 .. 在 Postgres 中选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11532550/