也不是啥难题,但是上周确确实实有两个简历上八年经验的人没答出来(这两个八年经验的小伙伴,一个是资深一个是高级)。
不过松哥的读者藏龙卧虎,相信在座的各位回答这道题没什么压力。
题目就是:不可重复读和幻读有什么区别
一 不可重复读 (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:
update t1 set age=age+1 where id>5;
假设表中有 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 隔离级别下,也是存在幻读问题的。