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

  • 如果是「小于等于」条件的范围查询,扫描到终止范围查询的记录时,该记录的主键索引中的 next-key 锁「不会」退化成间隙锁,其他扫描到的记录,都是在这些记录的主键索引上加 next-key 锁 。
  • 非唯一索引等值查询当我们用非唯一索引进行等值查询的时候,因为存在两个索引,一个是主键索引,一个是非唯一索引(二级索引),所以在加锁时,同时会对这两个索引都加锁,但是对主键索引加锁的时候,只有满足查询条件的记录才会对它们的主键索引加锁 。
    针对非唯一索引等值查询时,查询的记录存不存在,加锁的规则也会不同:
    • 当查询的记录「存在」时,由于不是唯一索引,所以肯定存在索引值相同的记录,于是非唯一索引等值查询的过程是一个扫描的过程,直到扫描到第一个不符合条件的二级索引记录就停止扫描,然后在扫描的过程中,对扫描到的二级索引记录加的是 next-key 锁,而对于第一个不符合条件的二级索引记录,该二级索引的 next-key 锁会退化成间隙锁 。同时,在符合查询条件的记录的主键索引上加记录锁 。
    • 当查询的记录「不存在」时,扫描到第一条不符合条件的二级索引记录,该二级索引的 next-key 锁会退化成间隙锁 。因为不存在满足查询条件的记录,所以不会对主键索引加锁 。
    接下里用两个实验来说明 。
    1、记录不存在的情况
    实验一:针对非唯一索引等值查询时,查询的值不存在的情况 。
    先来说说非唯一索引等值查询时,查询的记录不存在的情况,因为这个比较简单 。
    假设事务 A 对非唯一索引(age)进行了等值查询,且表中不存在 age = 25 的记录 。
    mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> select * from user where age = 25 for update;Empty set (0.00 sec)事务 A 加锁变化过程如下:
    • 定位到第一条不符合查询条件的二级索引记录,即扫描到 age = 39,于是该二级索引的 next-key 锁会退化成间隙锁,范围是 (22, 39) 。
    • 停止查询
    事务 A 在 age = 39 记录的二级索引上,加了 X 型的间隙锁,范围是 (22, 39) 。意味着其他事务无法插入 age 值为 23、24、25、26、....、38 这些新记录 。不过对于插入 age = 22 和 age = 39 记录的语句,在一些情况是可以成功插入的,而一些情况则无法成功插入,具体哪些情况,会在后面说 。
    MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

    文章插图
    我们也可以通过 select * from performance_schema.data_locks\G; 这条语句来看看事务 A 加了什么锁 。
    输出结果如下,我这里只截取了行级锁的内容 。
    MySQL 是怎么加行级锁的?为什么一会是 next-key 锁,一会是间隙锁,一会又是记录锁?

    文章插图
    从上图的分析,可以看到,事务 A 在 age = 39 记录的二级索引上(INDEX_NAME: index_age ),加了范围为 (22, 39) 的 X 型间隙锁 。
    此时,如果有其他事务插入了 age 值为 23、24、25、26、....、38 这些新记录,那么这些插入语句都会发生阻塞 。不过对于插入 age = 39 记录的语句,在一些情况是可以成功插入的,而一些情况则无法成功插入,具体哪些情况,接下来我们就说!
    当有一个事务持有二级索引的间隙锁 (22, 39) 时,什么情况下,可以让其他事务的插入 age = 22 或者 age = 39 记录的语句成功?又是什么情况下,插入 age = 22 或者 age = 39 记录时的语句会被阻塞?
    我们先要清楚,什么情况下插入语句会发生阻塞 。
    插入语句在插入一条记录之前,需要先定位到该记录在 B+树 的位置,如果插入的位置的下一条记录的索引上有间隙锁,才会发生阻塞 。

    经验总结扩展阅读