快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官( 二 )


快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
我们看上面的兼容性表,也得知表级的 IX 锁和表级的 X 锁是冲突的,所以刚刚好对应上这个场景 。
记录锁【快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官】记录锁是对索引记录的锁定,换句话说就是,记录锁只会锁定索引 。每一个表必定会有一个主键索引(用户定义的主键、唯一索引、隐式生成),而该主键索引中的非叶子节点中的记录就是使用该记录锁进行锁定 。
假设执行语句:select * from user where id = 10 for update;
如果 id 是 user 表中的主键,那么在主键索引中,id 为 10 的记录就会被锁定 。并且其他事务想要更新、删除此条记录都会被阻塞,只有等该记录中的记录锁被释放之后,才可以执行其他操作 。
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
除了主键索引之外,InnoDB 中还会有二级索引 。二级索引跟主键索引一样,在使用二级索引作为查询条件时,会将符合条件的二级索引的记录使用记录锁进行锁定,然后再回表将对应的主键索引也使用记录锁进行锁定 。
假设执行语句:select * from user where name = 'c' for update;
如果 id 是 user 表中的主键,name 是 user 表中的二级索引 。则会先将二级索引下的 name = ‘c’ 的索引锁定,然后再进行回表将主键索引为 9 的主键索引锁定 。
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
间隙锁间隙锁(简称为 Gap)是对索引记录之间的间隙的锁定,或者是对第一条索引记录之前的间隙和对最后一条记录之后的间隙的锁 。间隙锁是防止幻读的主要手段之一,幻读是同一个事务在不同的时间执行相同的查询语句,得出的结果集不同 。那么间隙锁是如何防止幻读的呢?实际上就是通过锁定指定的间隙,使得这些间隙无法插入新的记录,从而防止了数据的增长 。
假设我们执行此条语句:select * from user where id > 5 and id < 9 for update;
由于间隙锁的存在,其他事务如果想要插入 id 在 5 和 9 之间的记录是无法成功的,会被阻塞,直到间隙锁释放 。比如想要插入 id 为 6 的记录,就会阻塞,如下图所示(省略部分无关的字段) 。间隙锁跨越的间隙可能为一个值、多个值、甚至为空值 。
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
通过上图我们可以知道:
  • (5, 7]:id 为 5 的索引记录与 id 为 7 的索引记录之间的间隙被间隙锁锁定了
  • (7, 9]:id 为 7 的索引记录与 id 为 9 的索引记录之间的间隙被间隙锁锁定了
因为这两个间隙被间隙锁锁定了,所以在这两个间隙之间的记录是无法插入,只有等间隙锁释放之后才可以插入 。我们还要注意到,id 为 7 的记录是被记录锁锁定的,所以在 id 为 7 的记录上执行更新、删除操作时会被阻塞的 。
我们上面还说到,间隙锁还在第一条记录的前面和最后一条记录的后面加锁,我们来看看这是什么情况 。
假设我们执行此条语句:select * from user for update;
因为该语句没有使用索引,所以会进行全表扫描 。将扫描到的每一条记录都加上记录锁,并且将所有的间隙也加间隙锁 。最终的加锁情况如下图所示(省略部分无关的字段):
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
每个表中都会存在两个隐式记录:最小记录(infimum),最大记录(supermum)
我们通过上图,可以得出锁定的区间如下:
  • (-∞, 5]
  • (5, 7]
  • (7, 9]
  • (9, 10]
  • (10, 12]
  • (12, +∞)
并且所有的记录都被记录锁锁定 。这个看起来就像是一个表锁,因为对该表的任何操作(快照读除外),都会被阻塞 。
但是,间隙锁并不是在任何情况下都会使用,它在以下情况并不会使用:
  • 隔离级别为 RC、RU 。
  • 使用唯一索引进行等值比较获取一条索引记录 。这是因为唯一索引进行等值比较只能获取一条记录,不会出现多条记录的情况,那么也就不会出现多次读取出现不一致的情况 。
间隙锁的主要目的是阻止事务往间隙中插入记录,并且间隙锁之间是可以共存的,多个事务可以同时获取得到相同间隙的锁 。共享间隙锁和排他间隙锁之间并没有区别,它们是完全一样的东西 。
Next-Key 锁Next-Key 锁并不是一个难以理解的东西,它本质上就是索引记录上的记录锁和索引记录之间的间隙锁的结合 。


推荐阅读