MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?( 二 )


假设,表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录 。

MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
img
所以,next-key lock 即能保护该记录 , 又能阻止其他事务将新记录插入到被保护记录前面的间隙中 。
next-key lock 是包含间隙锁+记录锁的,如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的 。
比如,一个事务持有了范围为 (1, 10] 的 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,就会被阻塞 。
虽然相同范围的间隙锁是多个事务相互兼容的 , 但对于记录锁,我们是要考虑 X 型与 S 型关系,X 型的记录锁与 X 型的记录锁是冲突的 。
MySQL 是怎么加行级锁的?行级锁加锁规则比较复杂,不同的场景,加锁的形式是不同的 。
【MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?】加锁的对象是索引,加锁的基本单位是 next-key lock,它是由记录锁和间隙锁组合而成的,next-key lock 是前开后闭区间 , 而间隙锁是前开后开区间 。
但是,next-key lock 在一些场景下会退化成记录锁或间隙锁 。
那到底是什么场景呢?
这次会以下面这个表结构来进行实验说明:
CREATE TABLE `user` (  `id` bigint NOT NULL AUTO_INCREMENT,  `name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,  `age` int NOT NULL,  PRIMARY KEY (`id`),  KEY `index_age` (`age`) USING BTREE) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;其中,id 是主键索引(唯一索引) , age 是普通索引(非唯一索引) , name 是普通的列 。
表中的有这些行记录:
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
这次实验环境的 MySQL 版本是 8.0.26,隔离级别是「可重复读」 。
不同版本的加锁规则可能是不同的 , 但是大体上是相同的 。
唯一索引等值查询当我们用唯一索引进行等值查询的时候,查询的记录存不存在 , 加锁的规则也会不同:
  • 当查询的记录是「存在」的 , 在索引树上定位到这一条记录后,将该记录的索引中的 next-key lock 会退化成「记录锁」 。
  • 当查询的记录是「不存在」的 , 则会在索引树找到第一条大于该查询记录的记录 , 然后将该记录的索引中的 next-key lock 会退化成「间隙锁」 。
接下里用两个案例来说明 。
1、记录存在的情况假设事务 A 执行了这条等值查询语句,查询的记录是「存在」于表中的 。
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> select * from user where id = 1 for update;+----+--------+-----+| id | name   | age |+----+--------+-----+|  1 | 路飞   |  19 |+----+--------+-----+1 row in set (0.02 sec)那么,事务 A 会为 id 为 1 的这条记录就会加上 X 型的记录锁 。
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
接下来 , 如果有其他事务,对 id 为 1 的记录进行更新或者删除操作的话 , 这些操作都会被阻塞,因为更新或者删除操作也会对记录加 X 型的记录锁,而 X 锁和 X 锁之间是互斥关系 。
比如,下面这个例子:
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
因为事务 A 对 id = 1的记录加了 X 型的记录锁,所以事务 B 在修改 id=1 的记录时会被阻塞,事务 C 在删除 id=1 的记录时也会被阻塞 。
有什么命令可以分析加了什么锁?
我们可以通过 select * from performance_schema.data_locks\G; 这条语句 , 查看事务执行 SQL 过程中加了什么锁 。
我们以前面的事务 A 作为例子,分析下下它加了什么锁 。
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
从上图可以看到,共加了两个锁,分别是: