primefaces更新..在Postgres中select

我正在build立一个排队机制。 有一些需要处理的数据行和一个状态标志。 我正在使用update .. returning子句来pipe理它:

 UPDATE stuff SET computed = 'working' WHERE id = (SELECT id from STUFF WHERE computed IS NULL LIMIT 1) RETURNING * 

嵌套的select部分是否与更新相同,还是在这里有竞争条件? 如果是这样,内部select是否需要select for update

虽然Erwin的build议可能是获得正确行为的最简单方法(只要您在SQLSTATE为40001的情况下重试您的事务),排队应用程序的本质往往会更好地阻塞请求,从而有机会轮stream在队列中比在SERIALIZABLE事务的PostgreSQL实现,这允许更高的并发性,并对碰撞的机会有点“乐观”。

在问题中的示例查询,在默认的READ COMMITTED事务隔离级别下,将允许两个(或多个)并发连接,从队列中“声明”同一行。 会发生什么是这样的:

  • T1启动,并在UPDATE阶段locking行。
  • T2在执行时间与T1重叠,并尝试更新该行。 它会阻塞等待T1的COMMITROLLBACK
  • T1承诺,已经成功“声称”该行。
  • T2试图更新行,发现T1已经有,查找行的新版本,发现它仍然满足select标准(这正是id匹配),也“索赔”该行。

它可以被修改以正常工作(如果您正在使用允许子查询中的FOR UPDATE子句的PostgreSQL版本)。 只需将FOR UPDATE添加到selectID的子查询的末尾,就会发生:

  • T1开始,现在locking行,然后select ID。
  • T2在执行时间与T1重叠,并在尝试select一个ID时阻塞,等待T1的COMMITROLLBACK
  • T1承诺,已经成功“声称”该行。
  • 在T2能够读取该行以查看该id时,它看到它已被声明,因此它find下一个可用的id。

REPEATABLE READSERIALIZABLE事务隔离级别,写入冲突会引发一个错误,您可以捕获并确定基于SQLSTATE的序列化失败,然后重试。

如果您通常需要SERIALIZABLE事务,但您希望避免在排队区域重试,则可以使用咨询locking来完成此操作。

如果你是唯一的用户 ,查询应该没问题。 特别是,查询本身(外部查询和子查询之间)没有竞争条件或死锁。 我在这里引用手册:

但是,一个交易永远不会与自身冲突。

为了同时使用 ,事情可能会更复杂。 SERIALIZABLE交易模式可以保证您的安全:

 BEGIN ISOLATION LEVEL SERIALIZABLE; UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1) RETURNING * COMMIT; 

您需要准备序列化失败并在这种情况下重试您的查询。

但是我不完全确定这是不是过度的。 我会要求@kgrittn停止..他是并发和可序列化交易专家..他做到了。 🙂


两全其美

以默认事务模式运行查询READ COMMITTED并在外部UPDATE显式重新检查computed IS NULL条件:

 UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1) AND computed IS NULL ; 

正如@ kilrittn在他的回答中的评论中所build议的那样,这个查询可能在没有做任何事情的情况下是空的,在(不太可能的情况下)它与并发事务交织在一起。

因此,它将与事务模式SERIALIZABLE的第一个变体非常相似,您将不得不重试 – 只是没有性能损失。

唯一的问题是:虽然冲突是不太可能的,因为机会的窗口非常小,它可能在重负载下发生。 你不能确定是否最后没有剩下的行。

如果这不重要(就像你的情况),你在这里完成。
如果确实如此,那么在获得空的结果后,再启动一个带有显式locking的查询。 如果这是空的,你就完成了。 如果没有,继续。
在plpgsql中,它可能看起来像这样:

 LOOP UPDATE stuff SET computed = 'working' WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1) AND computed IS NULL ; 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 ); -- AND computed IS NULL; -- redundant here EXIT WHEN NOT FOUND; -- exit function (end) END LOOP; 

这应该给你两全其美:性能可靠性。