不可重复读和幻读有什么区别
也不是啥难题,但是上周确确实实有两个简历上八年经验的人没答出来(这两个八年经验的小伙伴,一个是资深一个是高级)。
不过松哥的读者藏龙卧虎,相信在座的各位回答这道题没什么压力。
题目就是:不可重复读和幻读有什么区别
一 不可重复读 (Non-Repeatable Read)
不可重复读是指在一个事务内多次读取同一数据的时候,由于其他事务对这些数据进行了修改并提交,导致读取的结果不一致。换句话说,在同一个事务中,如果两次读取之间有另一个事务修改了数据并提交,那么第一次读取和第二次读取可能会得到不同的结果。
举个简单例子:
事务 A 读取行 x = 100。
另一个事务 B 更新行 x 为 200 并提交。
事务 A 再次读取行 x,发现其值变为 200。
在这种情况下,事务 A 第一次读取到的数据与第二次读取到的数据不一致,即使两次读取操作都在同一个事务中进行。
这就是不可重复读,可以看到,不可重复读强调的是同一条数据被修改。
二 幻读 (Phantom Read)
幻读是指在一个事务内多次执行相同的查询时,由于其他事务插入或删除了一些记录,导致返回的记录数量或具体内容发生变化的现象。这种现象之所以被称为“幻读”,是因为原本不存在的记录似乎突然出现(或者消失),就像幽灵一样。
举个简单的例子:
事务 A 执行
SELECT * FROM table WHERE id > 5
,返回 n 条记录。另一个事务 B 插入了一条 id=6 的记录并提交。
事务 A 再次执行相同的查询,这次返回
n+1
条记录。
在这种情况下,事务 A 第一次查询到的记录数与第二次查询到的记录数不同,即使两次查询操作都在同一个事务中进行。
总结下就是:
不可重复读和幻读都是在同一个事务中执行相同的 SQL 多次,结果不同。在这个过程中,不可重复读说的是读取的记录被 update,而幻读则强调读取的记录中出现了 insert 或者 delete 操作。
三 连环追问
3.1 不可重复读和幻读分别是如何解决的
这里就涉及到两个关键的概念:MVCC 和 Gap Lock。
MVCC 和 Gap Lock 松哥之前都有专门的文章和大家聊过,这里就不啰嗦了,就和大家捋一捋思路。
MVCC 的核心思路就是在事务启动的一瞬间,给数据库表中的数据拍一张照片,接下来在整个事务执行期间,查询的数据都是从这张照片上查询,这样即使有其他的事务对表中的数据做了修改,也查不到。这样就解决了不可重复读问题了。
从表面上看,MVCC 似乎解决了幻读问题了,因为在当前事务执行期间,别的事务对表的修改都是不可见的,也就是别的事务插入或者删除了数据,当前事务也是不可见的。要从这个角度看,MVCC 确实在一定程度解决了幻读。
但是,这种 MVCC 解决幻读的方式更类似于一种“掩耳盗铃”的方式,因为别的事务还是可以插入和删除,只不过你自己看不见而已。
这块有的小伙伴可能会有疑问,数据修改了不可见就没问题,为啥插入和删除不可见就不行?这里涉及到一个锁的问题。给大家举个简单的案例:
现在 A 事务执行如下 SQL:
假设表中有 id 为 6、7、8 的记录,这个例子中,在事务提交之前,按理说 id>5 的记录都会被锁定,也就是 id 为 6、7、8 的记录会被锁定,锁定了就不能操作了。但是,你会发现能够插入 id 为 6 的记录,原因在于 id 为 6 的记录锁定了,但是 6 和 7 之间有缝隙,这个缝隙没锁定,所以可以往缝隙里插入数据,既然可以插入,也就可以删除,这边就造成 id 为 6 的记录似乎没锁定的观感。
当然,实际上这个问题是不存在的,因为 MySQL 中有一个 Gap Lock 就是用来解决这个问题的。
Gap Lock,也就是间隙锁,利用间隙锁将被操作记录两端的缝隙都锁住,这样就直接阻止了其他事务执行 insert 或者 delete 了。
因此,Gap Lock 和 MVCC 实际上互为补充,MVCC 解决了不可重复读问题,同时 MVCC 也解决了快照读的幻读问题,Gap Lock 则从源头上禁止插入和删除,来进一步辅助 MVCC 去解决幻读问题。
当然,说到 Gap Lock,往往还有两个相关的概念,Record Lock 和 Next-Key Lock,这块我就不展开了,不了解的小伙伴可以看下松哥之前的文章,一般来说这几个锁会连在一起提问,包括锁的退化问题都会在这里问到。
3.2 可重复读隔离级别是否存在幻读
有的人可能会歪打正着的把这个问题回答对。
按理说,MVCC+Gap Lock 已经解决了幻读问题了,所以一般理解 MySQL 默认的可重复读隔离级别就不存在幻读问题了。
没错,在可重复读隔离级别下,大部分的幻读都被解决了,但是也有例外。
例如:
如果两个事务,事务 A 先进行快照读,然后事务 B 插入了一条记录并提交,再在事务 A 中进行 update 新插入的这条记录,发现可以更新,这就是发生了幻读。
事务 A 先进行快照读,然后事务 B 插入了一条记录并提交,在事务 A 中进行了当前读之后,再进行快照读也会发生幻读。
所以,在 MySQL 的 RR 隔离级别下,也是存在幻读问题的。
评论