MySQL核心知识学习之路(7)
作为一个后端工程师,想必没有人没用过数据库,跟我一起复习一下MySQL吧,本文是我学习《MySQL实战45讲》的总结笔记的第七篇,总结了MySQL是如何解决幻读的。
上一篇:MySQL核心知识学习之路(6)
1 关于幻读
我们都知道MySQL的默认隔离级别是可重复读(点此复习MySQL的事务隔离),它仍然存在一个问题:幻读。
啥是幻读?
幻读指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行。
啥时候会出现幻读?
事务的隔离级别为可重复读,并且是当前读。
select * from t where id =1; -- 属于快照读。 select * from t where id =1 for update; -- 属于当前读。
一般情况下,幻读仅指新插入的行。
为何会出现幻读?
因为行锁只能锁定存在的行,针对新插入的操作没有限定。
幻读会带来啥问题?
对行锁语义的破坏 和 破坏了数据一致性。
如何解决幻读呢?
最简单的方法就是:升级事务隔离级别到可串行化,但这会让MySQL失去并发处理能力。
加之现有的行锁也解决不了幻读,因为即使锁住所有记录,还是阻止不了插入新数据。
所以,MySQL InnoDB引擎创造了一种新的锁,目的是锁住记录之间的“间隙”,且看下一部分的介绍。
2 间隙锁(gap lock)
啥是间隙锁?
顾名思义,间隙锁,锁的就是两个值之间的空隙。
比如下图所示的某张表t,在该表主键索引(id)上插入了6个记录(0,5,10,15,20,25),因此产生了7个间隙。
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
图片来源:林晓斌《MySQL实战45讲》
间隙锁是专门用于解决幻读这种问题的锁,它锁了行与行之间的间隙,这样就能够阻塞新插入的操作。
因此,划重点:当MySQL InnoDB引擎在一行行扫描的过程中,不仅会给行加上行锁,还会给行的两边的空隙也加上间隙锁。
补充:间隙锁之间是不冲突的,跟间隙锁存在冲突关系的只是“往这个间隙中插入一个记录”的操作。
注意事项
间隙锁在可重复读级别下才是有效的(换句话说,如果调整隔离级别为读提交就没有间隙锁了)。
间隙锁的引入也带来了一些新的问题,比如:降低并发度,可能导致死锁。
3 next-key lock
由于间隙锁(gap lock)仍在存在一些问题,可能会降低并发度和仍然可能导致死锁。因此,MySQL InnoDB为间隙锁引入了一个补充:next-key lock。
那么,问题来了:啥是next-key lock?
所谓next-key lock,它是间隙锁和行锁的合体,每个next-key lock都是前开后闭区间,如 (0,5]。
间隙锁都是开区间,如 (0,5)。
next-key lock帮助MySQL在默认隔离级别下解决了幻读问题,因此它也是MySQL加锁的基本单位。
MySQL加锁的原则
(1)加锁的基本单位是 next-key lock
(2)查找过程中访问到的对象才会加锁
该原则适用的前提条件为:
MySQL版本:5.x系列<=5.7.24,8.0系列<=8.0.13,且只有在可重复读隔离级别下(切换到读提交的话就只剩下行锁了)。
MySQL加锁的优化
索引上的等值查询,如果是给唯一索引加锁,此时next-key lock 退化为行锁。
索引上的等值查询,如果不是唯一索引,需要向右遍历访问到第一个值不满足等值条件的时候,此时next-key lock 退化为间隙锁。
MySQL加锁的Bug
唯一索引上的范围查询会访问到第一个不满足条件的值为止。
4 小结
本文总结了MySQL的InnoDB引擎是如何解决幻读问题的,即通过 间隙锁 + 行锁组成的next-key lock来实现的。
参考资料
林晓斌(丁奇),《MySQL实战45讲》
👇扫码订阅《MySQL实战45讲》