我正在构建一个排序的队列机制,有需要处理的数据行和一个状态标志,我使用update .. returning
子句来管理它:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id from STUFF WHERE computed IS NULL LIMIT 1)
RETURNING *
嵌套的选择部分是否与更新的锁相同,或者我在这里有一个争用条件?如果是这样,内部选择是否需要是select for update
?
我正在构建一个排序的队列机制,有需要处理的数据行和一个状态标志,我使用update .. returning
子句来管理它:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id from STUFF WHERE computed IS NULL LIMIT 1)
RETURNING *
嵌套的选择部分是否与更新的锁相同,或者我在这里有一个争用条件?如果是这样,内部选择是否需要是select for update
?
2条答案
按热度按时间igetnqfo1#
虽然欧文的建议可能是获得正确行为的“最简单”的方法(只要您在
SQLSTATE
为40001的情况下重试您的事务),排队应用程序本质上倾向于在请求阻塞以获得在队列中轮到它们的机会的情况下比在SERIALIZABLE
事务的PostgreSQL实现中工作得更好,这允许更高的并发性并且对冲突的可能性稍微更“乐观”。问题中的示例查询在默认的
READ COMMITTED
事务隔离级别下将允许两个(或更多)并发连接从队列中“认领”同一行。UPDATE
阶段锁定该行。COMMIT
或ROLLBACK
。id
匹配),并且还“声明”该行。可以修改它以使其正常工作(如果您使用的PostgreSQL版本允许在子查询中使用
FOR UPDATE
子句)。只需将FOR UPDATE
添加到选择id的子查询的末尾,就会发生以下情况:COMMIT
或ROLLBACK
。在
REPEATABLE READ
或SERIALIZABLE
事务隔离级别,写入冲突将引发错误,您可以捕获该错误并根据SQLSTATE确定序列化失败,然后重试。如果您通常需要SERIALIZABLE事务,但又希望避免在队列区域中重试,则可以通过使用建议锁来实现这一点。
vwkv1x7d2#
如果您是only用户,则查询应该没有问题。特别是,查询本身(外部查询和子查询之间)没有争用条件或死锁。手册:
但是,事务永远不会与自身冲突。
对于并发使用,事情可能会更复杂,安全起见,您可以使用
SERIALIZABLE
交易模式:您需要为序列化失败做好准备,并在这种情况下重试查询。
但我不完全确定这是否有点过头。我会让@kgrittn停下来..他是并发和可序列化事务的Maven..
And he did. :)
两全其美
在默认事务处理模式
READ COMMITTED
下运行查询。对于Postgres 9.5或更高版本,请使用
FOR UPDATE SKIP LOCKED
。请参见:对于旧版本,请在外部
UPDATE
中明确地重新检查条件computed IS NULL
:正如@kgrittn在他的回答的评论中所建议的那样,这个查询可能是空的,没有做任何事情,在(不太可能的)情况下,它与并发事务交织在一起。
因此,它的工作方式与事务模式
SERIALIZABLE
中的第一个变体非常相似,您必须重试-只是没有性能损失。唯一的问题是:虽然冲突发生的可能性很小,因为机会窗口实在太小了,但在负载很重的情况下也可能发生,您无法确定是否最终没有剩余的行。
如果这并不重要(就像你的情况),你在这里完成。
如果是这样的话,为了 * 绝对肯定 *,在得到一个空结果后,再启动一个带有 * 显式锁定 * 的查询。如果这个结果是空的,那么你就完成了。如果不是,继续。
在plpgsql中,它可能如下所示:
这应该能让你两全其美:性能和可靠性。