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


然后锁范围的「左边界」是表中 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)事务 A 加锁变化过程如下:
  1. 最开始要找的第一行是 id = 20,由于查询该记录不是一个等值查询(不是大于等于条件查询),所以对该主键索引加的是范围为 (15, 20] 的 next-key 锁;
  2. 由于是范围查找,就会继续往后找存在的记录,虽然我们看见表中最后一条记录是 id = 20 的记录,但是实际在 Innodb 存储引擎中,会用一个特殊的记录来标识最后一条记录,该特殊的记录的名字叫 supremum pseudo-record ,所以扫描第二行的时候,也就扫描到了这个特殊记录的时候,会对该主键索引加的是范围为 (20, +∞] 的 next-key 锁 。
  3. 停止扫描 。
可以得知,事务 A 在主键索引上加了两个 X 型 的 next-key 锁:
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
  • 在 id = 20 这条记录的主键索引上,加了范围为 (15, 20] 的 next-key 锁,意味着其他事务即无法更新或者删除 id = 20 的记录,同时无法插入 id 值为 16、17、18、19 的这一些新记录 。
  • 在特殊记录( supremum pseudo-record)的主键索引上,加了范围为 (20, +∞] 的 next-key 锁,意味着其他事务无法插入 id 值大于 20 的这一些新记录 。
我们也可以通过 select * from performance_schema.data_locks\G; 这条语句来看看事务 A 加了什么锁 。
输出结果如下,我这里只截取了行级锁的内容 。
MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

文章插图
从上图中的分析中,也可以得到事务 A 在主键索引上加了两个 X 型 的next-key 锁:

经验总结扩展阅读