0

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

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

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

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

我经常读到Repeatable ReadSerializable可以解决这个问题。甚至德国维基百科关于丢失更新的文章也指出了这一点。翻译成英文:

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

这个 SO answer建议Serializable在 SELECT 之后使用 INSERT 解决类似问题。

据我了解这个想法 - 在右侧的过程尝试设置帐户余额时,(理论上)读取操作将不再返回相同的余额。因此不允许写操作。是的 - 如果您阅读了这个流行的 SO 答案,它实际上听起来非常合适:

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

但后来我想知道“它们不能被删除或改变”实际上是什么意思。如果您尝试删除/更改它会发生什么?你会收到错误吗?还是您的事务会等到第一个事务完成并最终执行其更新?这一切都不同了。在第二种情况下,您仍然会赔钱。

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

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

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

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

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


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

4

2 回答 2

0

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

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

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

于 2022-03-03T13:09:46.650 回答
0

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

  • 一些 RDBMS 仅提供乐观锁定,Oracle 数据库和 PostGreSQL 就是这种情况
  • 其他一些 RDBMS 仅提供悲观锁定,IBM DB2 就是这种情况
  • 最后,Microsoft SQL Server 能够根据用户的选择交替使用乐观或悲观锁定,默认行为是悲观的

所以问题一定是你使用哪种RDBMS,你有哪种类型的锁定......

更多信息...

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

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

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

去夏...

下表给出了事务异常以及使用隔离级别时可以避免的情况:

隔离级别和事务异常

于 2022-03-03T11:58:33.090 回答