My Github

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讲》

 

posted @ 2021-04-07 16:49  EdisonZhou  阅读(238)  评论(0编辑  收藏  举报