kol*_*osy 46 postgresql concurrency multithreading race-condition transaction-isolation
我正在建立各种排队机制.有需要处理的数据行和状态标志.我正在使用一个update .. returning条款来管理它:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id from STUFF WHERE computed IS NULL LIMIT 1)
RETURNING *
Run Code Online (Sandbox Code Playgroud)
嵌套的选择部分是否与更新锁相同,或者我是否有竞争条件?如果是这样,内部选择需要是select for update吗?
kgr*_*ttn 36
虽然Erwin的建议可能是获得正确行为的最简单方法(只要你用SQLSTATE40001 获得异常就重试你的事务),按照他们的性质对应用程序进行排队往往会更好地处理阻止有机会轮到他们的请求队列比PostgreSQL实现的SERIALIZABLE事务,它允许更高的并发性,并且对碰撞的可能性更加"乐观".
问题中的示例查询(如图所示)在默认READ COMMITTED事务隔离级别中将允许两个(或更多)并发连接"声明"队列中的同一行.会发生什么是这样的:
UPDATE阶段中的行.COMMIT或ROLLBACKT1.id匹配),并且还"声明"该行.它可以被修改为正常工作(如果您使用的是允许FOR UPDATE子查询中的子句的PostgreSQL版本).只需添加FOR UPDATE到选择id的子查询的末尾,这将发生:
COMMIT或ROLLBACKT1.在REPEATABLE READ或SERIALIZABLE事务隔离级别,写入冲突会引发错误,您可以捕获并确定基于SQLSTATE的序列化失败,然后重试.
如果您通常需要SERIALIZABLE事务但是想要避免在排队区域中重试,则可以通过使用建议锁来实现此目的.
Erw*_*ter 21
如果您是唯一的用户,则查询应该没问题.特别是,查询本身(外部查询和子查询之间)没有竞争条件或死锁.我在这里引用手册:
但是,事务永远不会与自身冲突.
对于同时使用,问题可能更复杂.您可以使用SERIALIZABLE交易模式保证安全:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
RETURNING *
COMMIT;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您需要准备序列化失败并重试查询.
但我不完全确定这不是矫枉过正.我会要求@kgrittn停下来..他是并发和可序列化事务的专家.
在默认事务模式下运行查询READ COMMITTED.
对于Postgres 9.5或更高版本使用FOR UPDATE SKIP LOCKED.看到:
对于旧版本computed IS NULL,在外部显式重新检查条件UPDATE:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
AND computed IS NULL;
Run Code Online (Sandbox Code Playgroud)
正如@ kgrittn在他的回答评论中所建议的那样,这个查询可能在没有做任何事情的情况下变得空洞,在(不太可能的)情况下它与并发事务交织在一起.
因此,它将像交易模式中的第一个变体一样工作SERIALIZABLE,您必须重试 - 只是没有性能损失.
唯一的问题是:虽然冲突是不太可能的,因为机会之窗非常小,但它可能在重负荷下发生.你无法确定是否最后没有剩下的行.
如果这无关紧要(就像你的情况一样),你就完成了.
如果是的话,是绝对的把握,开始多了一个查询明确锁定你会得到一个空的结果后.如果这个空了,你就完成了.如果没有,继续.
在plpgsql中它可能如下所示:
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;
Run Code Online (Sandbox Code Playgroud)
这应该会给你两全其美:性能和可靠性.
| 归档时间: |
|
| 查看次数: |
16451 次 |
| 最近记录: |