关于数据库丢失更新问题和已提交读的关系

2021-04-25 14:32:49 +08:00
 zxCoder

重新学习事务,看到这部分有点迷糊,网上的资料又比较乱,不知道谁说得靠谱

一开始看了《数据密集型应用系统设计》这本书

里面说

防止丢失更新 到目前为止已经讨论的读已提交和快照隔离级别,主要保证了只读事务在并发写入时可以看到什么。却忽略了两个事务并发写入的问题——我们只讨论了脏写,一种特定类型的写-写冲突是可能出现的。 并发的写入事务之间还有其他几种有趣的冲突。其中最着名的是丢失更新( lost update ) 问题,如图 7-1 所示,以两个并发计数器增量为例。

这里我不太理解,读写都能在读已提交的隔离级别下解决,为什么写写还会产生冲突呢?读已提交是说只能读取一个已提交的数据,难道写不需要先读吗?读已提交下写可以覆盖一个未提交的数据?

377 次点击
所在节点    问与答
1 条回复
timethinker
2021-04-25 15:25:48 +08:00
读已提交并不会覆盖未提交的数据,而是覆盖了另一个已提交的数据。

一般来讲,在读已提交的隔离级别下,如果两个事务试图尝试对同一条记录进行更改,那么就会推迟第二个事务的写,直至第一个事务提交或终止,具体采用的就是行级锁。

比如,两个并发的请求,尝试对一个计数器增加 1 的操作,很显然其中一个需要等待另一个事务提交或终止。
A 读取了计数器,假设为 100,+1,写回。
B 读取了计数器,假设为 100,+1,写回。
两个事务在开始的时候都读取的 100,但是经过自己+1 之后,都是 101,在写回的时候就会出现问题(业务上正确的应该是 102 才对)。
实际上这种操作属于覆盖,在读和写之间并不是原子的,也就是一个事务覆盖了另一个事务的写,破坏了数据的完整性,这就是丢失更新问题。就像是在 Java 多线程并发编程中,如果你不把一个静态变量声明为 volatile,且在加锁的代码块里面再次判断这个变量是否等于之前已知的状态,就有可能会发生覆盖(原子比较+设置),例如 Java 的单例双重检查。

解决这个问题可以对行数据增加乐观锁版本号(也就是原子比较+设置),在事务中对写入结果进行判断,例如:
UPDATE counter SET count = 101, version = 1 WHERE version = 0
如果结果返回影响数据为 0 条(代码判断),则终止当前事务,防止发生覆盖。也可以用 SELECT FOR UPDATE,加独占锁,这样另一个事物在读取同一条数据的时候会被阻塞。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/773113

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX