提问者:小点点

使用高事务隔离级别防止丢失更新:这是一个常见的误解吗?


我注意到我的应用程序经常将值写入依赖于以前的读取操作的数据库。一个常见的例子是用户可以存钱的银行账户:

void deposit(amount) {
    balance = getAccountBalance()
    setAccountBalance(balance + amount)
}

我想避免竞争条件,如果这个方法被两个线程/客户端/ATM同时调用,就像这样,账户所有者会赔钱:

balance = getAccountBalance()       |
                                    | balance = getAccountBalance()
setAccountBalance(balance + amount) |
                                    | // balance2 = getAccountBalance() // theoretical
                                    | setAccountBalance(balance + amount)
                                    V

我经常读到可重复读取或可序列化可以解决这个问题。甚至德文维基百科的丢失更新文章也这样说。翻译成英文:

隔离级别RR(可重复读取)经常被认为是丢失更新问题的解决方案。

这个SO的答案建议在SELECT之后对INSERT的类似问题进行序列化。

据我所知——当右侧的进程试图设置账户余额时,(理论上的)读取操作将不再返回相同的余额。因此不允许写操作。是的——如果你读到这个流行的SO答案,它实际上听起来非常合适:

在REPEATABLE READ下,第二个SELECT保证至少显示从第一个SELECT返回的未更改的行。在这一分钟内,可以通过并发事务添加新行,但不能删除或更改现有行。

但后来我想知道“它们不能被删除或更改”实际上意味着什么。如果你无论如何都试图删除/更改它会发生什么?你会得到一个错误吗?或者你的交易会等到第一笔交易完成,最后也执行它的更新吗?这一切都不同了。在第二种情况下,你仍然会赔钱。

如果你阅读下面的注释,情况会变得更糟,因为还有其他方法可以满足可重复读取条件。例如快照技术:可以在左侧事务写入其值之前拍摄快照,这允许在右侧事务稍后发生第二次读取时提供原始值。例如,请参阅MySQL手册:

同一事务内的一致性读取读取第一次读取建立的快照

我得出的结论是,限制事务隔离级别可能是摆脱竞争条件的错误工具。如果它解决了问题(对于特定的DBMS),这不是由于可重复读取的定义。而是因为满足可重复读取条件的特定实现。例如锁的使用。

所以,对我来说,它看起来像这样:您实际需要解决这个问题的是一种锁定机制。一些DBMS使用锁来实现可重复读取的事实被利用了。

这个假设正确吗?还是我对事务隔离级别的理解有误?

你可能会生气,因为这一定是关于这个话题的第一百万个问题。问题是:示例银行账户场景绝对至关重要。就在那里,应该绝对清楚发生了什么,在我看来,似乎有太多误导和矛盾的信息和误解。


共3个答案

匿名用户

这里的问题是,您问的是,按照SQL标准的定义,需要什么隔离级别来解决不属于此定义的并发异常。

SQL标准只定义了隔离级别(读未提交读已提交可重复读可串行化)如何映射到脏读不可重复读幻读异常。没有提到Love-Update,所以正如您正确指出的那样,这取决于特定DBMS如何实现隔离级别。

据说REPEATABLE_READ足以防止PostgreSQL上的Lows-Update,而在MySQL和Oracle上需要SERIALIZABLE来防止它。

这里有一些关于Oracle和PostgreSQL/MySQL的有趣帖子

匿名用户

在SQL服务器中,REPEATABLE READ和SERIALIZABLE都将通过使一个带有死锁的事务失败来防止丢失更新。在这些隔离级别中,每个会话将在初始SELECT期间在目标行上获得并持有一个共享(S)锁。然后每个会话将尝试在该行上获得一个排他(X)锁来更新它,从而导致死锁。

如果您想避免丢失更新,而不让一个会话等待另一个会话完成,您必须在初始选择之前或期间创建一个更排他的锁。通常的模式是在初始选择中添加UPDLOCK提示以指示“选择更新”。并且使用“选择更新”没有理由提高事务隔离级别。

Oracle和PostgreSQL也有可以使用的“选择更新”语法。

匿名用户

丢失更新是一种事务异常,只有当事务使用乐观锁定时才会发生。它永远不会发生在悲观锁定中。

  • 有些RDBMS只提供乐观锁定,Oracle Database和PostGreSQL就是这种情况
  • 其他一些RDBMS只提供悲观锁定,IBMDB2就是这种情况
  • 最后,MicrosoftSQLServer能够根据用户的选择交替使用乐观或悲观锁定,默认行为为悲观

所以问题必须面对你使用哪种RDBMS,你有哪种类型的锁…

更多的信息…

保证执行写入的事务成功完成只有在以排他模式锁定开始时才有可能,同时通过确保锁定模式是悲观的而不是乐观的来在事务期间保持锁。尽管如此,这种技术不会防止死锁…

数学家Edsger Dijkstra解决了最后一个问题(银行家算法),表明在开始更新数据(INSERT、UPDATE、DELETE…)之前,有必要设置所有必要的锁来保护处理的数据,这相当于只能独占访问所有处理数据… Dijkstra因对计算机科学的贡献而获得图灵奖!

换句话说,只有一个访问数据库的用户!…

为了总结…

下表给出了事务异常以及使用隔离级别时应避免的问题: