Atomic UPDATE .. SELECT in Postgres
我正在建立各种排队机制。 有需要处理的数据行和状态标志。 我正在使用
1 2 3 4 | UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM STUFF WHERE computed IS NULL LIMIT 1) RETURNING * |
嵌套的选择部分是否与更新锁相同,或者我是否有竞争条件? 如果是这样,内部选择需要是
虽然Erwin的建议可能是获得正确行为的最简单方法(只要你在
在问题中的示例查询(在默认的
-
T1启动并获得锁定
UPDATE 阶段中的行。 -
T2在执行时间内重叠T1并尝试更新该行。它阻止等待T1的
COMMIT 或ROLLBACK 。 - T1提交,已成功"声称"该行。
-
T2尝试更新行,发现T1已经拥有,查找该行的新版本,发现它仍然满足选择条件(只是
id 匹配),并且还"声明"该行。
它可以被修改为正常工作(如果您使用的是PostgreSQL版本,它允许子查询中的
- T1启动,现在在选择id之前锁定行。
-
T2在执行时间内重叠T1并在尝试选择id时阻塞,等待T1的
COMMIT 或ROLLBACK 。 - T1提交,已成功"声称"该行。
- 当T2能够读取行以查看id时,它会看到它已被声明,因此它找到下一个可用的id。
在
如果您通常需要SERIALIZABLE事务但是想要避免在排队区域中重试,则可以通过使用建议锁来实现此目的。
如果您是唯一的用户,则查询应该没问题。特别是,查询本身(外部查询和子查询之间)没有竞争条件或死锁。我在这里引用手册:
However, a transaction never conflicts with itself.
对于同时使用,问题可能更复杂。使用
1 2 3 4 5 6 | BEGIN ISOLATION LEVEL SERIALIZABLE; UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1) RETURNING * COMMIT; |
在这种情况下,您需要准备序列化失败并重试查询。
但我不完全确定这不是矫枉过正。我会要求@kgrittn停下来..他是并发和可序列化事务的专家。
他做到了。 :)
两全其美
以默认事务模式
对于Postgres 9.5或更高版本,请使用
- Postgres UPDATE ... LIMIT 1
对于旧版本,在外部
1 2 3 4 | UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1) AND computed IS NULL; |
正如@ kgrittn在他的回答评论中所建议的那样,这个查询可能在没有做任何事情的情况下变得空洞,在(不太可能的)情况下它与并发事务交织在一起。
因此,它将像事务模式
唯一的问题是:虽然冲突是不太可能的,因为机会之窗非常小,但它可能在重负荷下发生。你无法确定是否最后没有剩下的行。
如果这无关紧要(就像你的情况一样),你就完成了。
如果确实如此,那么在获得空结果后,再使用显式锁定再启动一个查询。如果这个空了,你就完成了。如果没有,继续。
在plpgsql中它可能如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | LOOP UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1 FOR UPDATE SKIP LOCKED); -- pg 9.5+ -- WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1) -- AND computed IS NULL; -- pg 9.4- CONTINUE WHEN FOUND; -- continue outside loop, may be a nested loop UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1 FOR UPDATE); EXIT WHEN NOT FOUND; -- exit function (end) END LOOP; |
这应该会给你两全其美:性能和可靠性。