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

这里我们重点关注行级锁,图中 LOCK_TYPE 中的 RECORD 表示行级锁,而不是记录锁的意思 。
通过 LOCK_MODE 可以确认是 next-key 锁,还是间隙锁 , 还是记录锁:

  • 如果 LOCK_MODE 为 X,说明是 next-key 锁;
  • 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁;
  • 如果 LOCK_MODE 为 X, GAP , 说明是间隙锁;
因此,此时事务 A 在 id = 1 记录的主键索引上加的是记录锁,锁住的范围是 id 为 1 的这条记录 。这样其他事务就无法对 id 为 1 的这条记录进行更新和删除操作了 。
从这里我们也可以得知,加锁的对象是针对索引 , 因为这里查询语句扫描的 B+ 树是聚簇索引树,即主键索引树 , 所以是对主键索引加锁 。将对应记录的主键索引加 记录锁后,就意味着其他事务无法对该记录进行更新和删除操作了 。
2、记录不存在的情况假设事务 A 执行了这条等值查询语句,查询的记录是「不存在」于表中的 。
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> select * from user where id = 2 for update;Empty set (0.03 sec)接下来,通过 select * from performance_schema.data_locks\G; 这条语句 , 查看事务执行 SQL 过程中加了什么锁 。
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
从上图可以看到,共加了两个锁,分别是:
  • 表锁:X 类型的意向锁;
  • 行锁:X 类型的间隙锁;
因此 , 此时事务 A 在 id = 5 记录的主键索引上加的是间隙锁,锁住的范围是 (1, 5) 。
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
接下来,如果有其他事务插入 id 值为 2、3、4 这一些记录的话,这些插入语句都会发生阻塞 。
注意,如果其他事务插入的 id = 1 或者 id = 5 的记录话 , 并不会发生阻塞,而是报主键冲突的错误 , 因为表中已经存在 id = 1 和 id = 5 的记录了 。
比如,下面这个例子:
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
因为事务 A 在 id = 5 记录的主键索引上加了范围为 (1, 5) 的 X 型间隙锁 , 所以事务 B 在插入一条 id 为 3 的记录时会被阻塞住 , 即无法插入 id = 3 的记录 。
间隙锁的范围(1, 5),是怎么确定的?
根据我的经验 , 如果 LOCK_MODE 是 next-key 锁或者间隙锁,那么 LOCK_DATA 就表示锁的范围「右边界」,此次的事务 A 的 LOCK_DATA 是 5 。
然后锁范围的「左边界」是表中 id 为 5 的上一条记录的 id 值 , 即 1 。
因此,间隙锁的范围(1, 5)
唯一索引范围查询范围查询和等值查询的加锁规则是不同的 。
当唯一索引进行范围查询时,会对每一个扫描到的索引加 next-key 锁,然后如果遇到下面这些情况,会退化成记录锁或者间隙锁:
  • 情况一:针对「大于等于」的范围查询,因为存在等值查询的条件 , 那么如果等值查询的记录是存在于表中,那么该记录的索引中的 next-key 锁会退化成记录锁 。
  • 情况二:针对「小于或者小于等于」的范围查询,要看条件值的记录是否存在于表中:
    • 当条件值的记录不在表中,那么不管是「小于」还是「小于等于」条件的范围查询,扫描到终止范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙锁,其他扫描到的记录,都是在这些记录的索引上加 next-key 锁 。
    • 当条件值的记录在表中 , 如果是「小于」条件的范围查询,扫描到终止范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙锁,其他扫描到的记录 , 都是在这些记录的索引上加 next-key 锁;如果「小于等于」条件的范围查询,扫描到终止范围查询的记录时 , 该记录的索引 next-key 锁不会退化成间隙锁 。其他扫描到的记录,都是在这些记录的索引上加 next-key 锁 。
接下来,通过几个实验,才验证我上面说的结论 。
1、针对「大于或者大于等于」的范围查询
实验一:针对「大于」的范围查询的情况 。
假设事务 A 执行了这条范围查询语句:
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> select * from user where id > 15 for update;+----+-----------+-----+| id | name      | age |+----+-----------+-----+| 20 | 香克斯    |  39 |+----+-----------+-----+1 row in set (0.01 sec)

推荐阅读