【发布时间】:2022-12-16 07:08:45
【问题描述】:
我注意到我的应用程序经常将值写入数据库,这取决于以前的读取操作。一个常见的例子是用户可以存钱的银行账户:
void deposit(amount) {
balance = getAccountBalance()
setAccountBalance(balance + amount)
}
如果此方法同时被两个线程/客户端/ATM 调用,我想避免竞争条件,这样帐户所有者会赔钱:
balance = getAccountBalance() |
| balance = getAccountBalance()
setAccountBalance(balance + amount) |
| // balance2 = getAccountBalance() // theoretical
| setAccountBalance(balance + amount)
V
我经常读到可重复读或者可序列化可以解决这个问题。即使是 german Wikipedia article for Lost Updates 也说明了这一点。翻译成英文:
隔离级别 RR(Repeatable Read)经常被提及作为丢失更新问题的解决方案。
This SO answer建议可序列化对于 SELECT 之后的 INSERT 的类似问题。
据我了解这个想法 - 在右侧的过程试图设置账户余额时,(理论上的)读取操作不会再返回相同的余额。因此写操作是不允许的。是的——如果你阅读this popular SO answer,它实际上听起来非常合适:
在 REPEATABLE READ 下,第二个 SELECT 保证至少显示从第一个 SELECT 返回的未更改的行。在那一分钟内,并发事务可能会添加新行,但无法删除或更改现有行。
但后来我想知道什么“它们不能被删除或改变”实际上意味着。如果您仍然尝试删除/更改它会发生什么?你会得到一个错误吗?或者您的交易会等到第一个交易完成并最终执行更新吗?这一切都不同了。在第二种情况下,您仍然会赔钱。
如果你阅读下面的 cmets,它会变得更糟,因为还有其他方法可以满足可重复读条件。例如快照技术:可以在左侧事务写入其值之前拍摄快照,如果稍后在右侧事务中发生第二次读取,这允许提供原始值。例如,请参阅MySQL manual:
同一事务内的一致性读取读取第一次读取建立的快照
我得出的结论是,限制事务隔离级别可能是消除竞争条件的错误工具。如果它解决了问题(对于特定的 DBMS),则不是由于定义可重复读.而是因为特定的实现来实现可重复读条件。例如锁的使用。
所以,对我来说它看起来像这样:解决这个问题你真正需要的是一个锁定机制。一些 DBMS 使用锁来实现的事实可重复读被剥削。
这个假设是否正确?还是我对事务隔离级别的理解有误?
您可能会生气,因为这一定是关于该主题的第一百万个问题。问题是:示例银行账户场景绝对关键。就在那儿,应该绝对清楚发生了什么,但在我看来,似乎有太多误导和矛盾的信息和误解。
【问题讨论】:
-
对于特定的银行情况,这就是为什么他们通常记录(银行)交易.单独的交易记录为单独的行,余额本身可能实际上是一个计算值。
-
银行账户只是一个例子。但无论如何,我想知道如果你有数百万次转移,你是否还想计算价值。另外,我并不是说更新问题无法解决。例如,您可以实施“乐观锁”。我只是想确认高隔离级别不是一个实际的解决方案,尽管通常以其他方式传播。
标签: sql transactions isolation-level transaction-isolation snapshot-isolation