当克雷格·林格(Craig Ringer)评论时,我不明白他的意思:
如果插入事务回滚,则此解决方案可能会丢失更新。没有检查来强制UPDATE影响任何行。
在https://stackoverflow.com/a/8702291/14731上。请提供事件的样本序列(例如,线程1执行X,线程2执行Y),以演示丢失更新的发生方式。
当克雷格·林格(Craig Ringer)评论时,我不明白他的意思:
如果插入事务回滚,则此解决方案可能会丢失更新。没有检查来强制UPDATE影响任何行。
在https://stackoverflow.com/a/8702291/14731上。请提供事件的样本序列(例如,线程1执行X,线程2执行Y),以演示丢失更新的发生方式。
Answers:
我想我可能打算在先前的答案中添加有关两个单独陈述的评论。一年多以前,所以我现在还不确定。
基于wCTE的查询并不能真正解决它应该解决的问题,但是在一年后再次进行检查后,我看不到wCTE版本中丢失更新的可能性。
(请注意,所有这些解决方案只有在尝试对每个事务更改一行时才有效。当您尝试对一个事务进行多次更改时,由于需要回滚中的重试循环,事情会变得一团糟。您需要在每次更改之间使用一个保存点。)
除非应用程序从该UPDATE
语句和该INSERT
语句检查受影响的行数,并且如果两者均为零,则重试,否则使用两个单独的语句的版本可能会丢失更新。
想象一下,如果您有两个READ COMMITTED
隔离的事务会发生什么。
UPDATE
(无效果)INSERT
(插入一行)UPDATE
(无效,TX1插入的行尚不可见)COMMIT
秒。INSERT
,*,它将获得一个新快照,该快照可以查看TX1提交的行。该EXISTS
子句返回true,因为TX2现在可以看到TX1插入的行。因此TX2无效。除非应用程序检查更新中的行计数以及插入和重试(如果两者均报告为零行),则它将不知道该交易没有任何效果,并会愉快地进行下去。
它可以检查受影响的行数的唯一方法是将其作为两个单独的语句而不是多语句运行,或使用一个过程。
您可以使用SERIALIZABLE
隔离,但是仍然需要重试循环来处理序列化失败。
wCTE版本可防止丢失更新问题,因为这INSERT
取决于是否UPDATE
影响任何行,而不是单独的查询。
可写的CTE版本仍然不是可靠的补充。
考虑两个同时运行的事务。
两者都执行VALUES子句。
现在,他们两个都执行该UPDATE
部分。由于没有与UPDATE
s where子句匹配的行,因此两者都从更新返回空结果集,并且不做任何更改。
现在都运行该INSERT
部分。由于UPDATE
两个查询都返回了零行,因此两者都尝试访问INSERT
该行。
一个成功。一个人抛出一个独特的违规并中止。
只要应用程序检查其查询(例如写得不错的应用程序)的错误结果并重试,就不必担心数据丢失,但是它使解决方案不比现有的两个语句版本更好。它并没有消除重试循环的需要。
wCTE提供的优于现有的两个语句版本的优势在于,它使用的输出UPDATE
来确定是否to INSERT
,而不是对表使用单独的查询。这在某种程度上是一种优化,但是在某种程度上可以防止两语句版本导致更新丢失的问题。见下文。
您可以单独运行wCTE SERIALIZABLE
,但随后只会序列化失败,而不会出现唯一违规。它不会改变重试循环的需要。
我的评论表明,此解决方案可能会导致更新丢失,但经审查后,我认为我可能会误会。
它已经一年多了,我不记得确切的情况了,但是我想我可能会错过这样一个事实,即唯一索引具有事务可见性规则的部分例外,以便允许一个插入的事务等待另一个插入或滚动的事务在继续之前返回。
也许我错过了一个事实,即INSERT
wCTE中的条件取决于是否UPDATE
影响任何行,而不取决于表中是否存在候选行。
INSERT
唯一索引上的冲突,等待提交/回滚
假设查询的一个副本正在运行,并插入一行。更改尚未提交。新的元组存在于堆和唯一索引中,但是,无论隔离级别如何,它都对其他事务不可见。
现在,该查询的另一个副本将运行。由于第一个副本尚未提交,因此插入的行尚不可见,因此更新不匹配任何内容。查询将继续尝试插入,这将看到另一个正在进行的事务正在插入相同的键,并且将阻止等待该事务提交或回滚。
如果第一个事务提交,则第二个事务将失败,并发生唯一的违规(根据以上所述)。如果第一个事务回滚,则第二个事务将继续执行其插入操作。
将INSERT
依赖于UPDATE
行数可以防止丢失更新
与两个语句的情况不同,我认为wCTE不容易丢失更新。
如果UPDATE
无效,则INSERT
它将始终运行,因为它严格取决于是否执行UPDATE
任何操作,而不是外部表状态。因此它仍然可能因独特的违规而失败,但它不会无声地失效并完全丢失更新。