Mysql 行锁(记录锁、间隙锁、临键锁)研究,基于InnoDB

行锁简介
通过上一章可以了解到,InnoDB下行锁可细分为记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next_Key Lock),是基于索引实现的,其本质上是三种加锁算法。ps:若不声明,默认采用RR隔离级别。

注:有些把记录锁理解为行锁,RL,GL,NKL属于同一级别,都是排他锁的一种。

记录锁:锁精确加在某一行上。
间隙锁:锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记录之前(负无穷,first-key)或最后一个索引之后(last-key,正无穷)的无限空间。
临键锁:记录锁与间隙锁组合起来用就叫做Next-Key Lock,就是将键及其两边的的间隙加锁(向左扫描扫到第一个比给定参数小的值, 向右扫描扫描到第一个比给定参数大的值, 然后以此为界,构建一个区间)。
利用Next-Key Lock可以阻止其它事务在锁定区间内插入数据,因此在一定程度上解决了幻读问题。
eg:构建表test(id pk,num key)。

idnum
33
55
77
107
149
1610
2013

记录锁
记录锁很好理解,一般要通过主键或唯一索引加锁,就可以较好的实现。

  select * from test where id=6 for update;//该句可以准确且只锁id=6这一行

间隙锁
间隙锁锁定的时一个开区间,而不是某个键,它是基于非唯一索引。需要注意的是用非唯一索引时,要explain下,确保sql走了索引(mysql查询优化器认为全表扫描比用索引更快时会锁表)。那么什么情况下出现间隙所呢?
首先:InnoDB存储引擎,采用RR隔离级别,参数innodb_locks_unsafe_for_binlog=0(静态参数,默认为0,表示启动gap lock,如果设置为1,表示禁用gap lock,该参数最新版本已被弃用)。
1:唯一索引/主键+范围查询
锁住范围内的已存在的符合要求的行,还会加上范围内的gap,例子如下:

select * from test where id between 6 and 16 for update;//该句锁id=7,10,15这三行,
还会锁id=8,9,11,12,13,14这几行,  此时另一个事务是插不进去id=8,9,11,12,13,14的数据的。

2:普通索引+绝对范围(不存在等于的情况)查询
锁住范围内的已存在的符合要求的,还会加上范围内的gap,例子如下:

事务A:
select * from test where num>10 for update;//该句锁定范围(10,正无穷),不包括10。

事务B:
insert into test (id,num)  values(15,10)//成功
 update test set num=18 where id=16//成功 *
  insert into test (id,num)  values(17,10)//失败*
   insert into test (id,num)  values(17,9)//成功*

3:普通索引+等值查询
除了锁定值外,还会加上左右两侧的gap,例子如下:

事务A:
select * from test where num = 7 for update;//该句除了锁定(5,9)  

事务B:
insert into test (id,num)  values(4,5)//成功
 update test set num=18 where id=5//成功 *
  insert into test (id,num)  values(6,5)//失败*
   insert into test (id,num)  values(6,20)//成功*
    insert into test (id,num)  values(6,8)//失败          

对于普通索引需要注意的是,锁范围的边缘值可以更新,但不允许在锁定范围内插入与边缘值相等的行。如例子中加*的sql。

另外,对于2,如果是 select * from test where num>=9;就会锁定范围(7,正无穷),而不是[9,正无穷),但是select * from test where id>=16;就会锁定范围[16,正无穷),而不是(14,正无穷)。

要想明白这个,首先要了解InoDB的索引,在InnoDB表就是一个索引组织表(IOT),他的主键就是它的聚族索引(InnoDB默认对主键建立聚簇索引。如果你不指定主键,InnoDB会用一个具有唯一且非空值的索引来代替。如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键是一个6个字节的列,该列的值会随着数据的插入自增),而普通索引作为非聚族索引,就会有一个普通索引值对多个主键值(如【7,7】,【10,7】),因此mysql对于二级索引和一级索引的间隙锁处理机制不同的,二级索引(辅助)允许插入相同的值,为了防止在锁定范围前插入边缘值,所以直接锁定范围外的第一个gap。

临键锁 参考链接
在默认情况下,mysql的事务隔离级别是RR,并且innodb_locks_unsafe_for_binlog=0,这时默认采用next-key locks.所谓Next-Key Lock,就是Record lock和gap lock的结合。

分析
下面我们针对大部分的SQL类型分析是如何加锁的,假设事务隔离级别为可重复读。
select … from
不加任何类型的锁

select…from lock in share mode
在扫描到的任何索引记录上加共享的(shared)next-key lock,对于主键/唯一索引加共享锁

select…from for update
在扫描到的任何索引记录上加排它的next-key lock,对于扫描到的主键/唯一索引加记录锁 ,对于不存在的加间隙锁

update…where delete from…where
在扫描到的任何索引记录上加next-key lock,对于扫描到的主键/唯一索引加记录锁 ,对于不存在的加间隙锁

insert into…
简单的insert会在insert的行对应的索引记录上加一个排它锁,这是一个record lock,并没有gap,所以并不会阻塞其他session在gap间隙里插入记录。不过若insert操作在间隙锁范围内,就会加另一种锁,官方文档称它为insertion intention gap lock,参考插入意向间隙锁解析,也就是意向的gap锁。这个意向gap锁的作用就是预示着当多事务并发插入相同的gap空隙时,只要插入的记录不是gap间隙中的相同位置,则无需等待其他session就可完成,这样就使得insert操作无须加真正的gap lock
这样的插入机制,会很大程度上提升并发性,如果一个表有一个唯一索引id,表中有记录1和8,那么当(1,8)不存在间隙锁,每个事务都可以在[2,7]之间插入任何记录,只会对当前插入的记录加record lock,并不会阻塞其他session插入与自己不同的记录,因为他们并没有任何冲突;当存在间隙锁,其他session就不能插入任何记录,直到间隙锁被释放,这样可以防止幻读。
《Mysql 行锁(记录锁、间隙锁、临键锁)研究,基于InnoDB》
可以看到T2时会话2对(1,8)加间隙锁,所以T3会话1会获得插入意向锁,被阻塞,而T4时会话2可以正常插入,并对id=6加记录锁。

行锁的兼容矩阵
《Mysql 行锁(记录锁、间隙锁、临键锁)研究,基于InnoDB》
可以看到,Gap与Gap是互相兼容的 ,请求IIGL与当前Gap是冲突的,参考文章

    原文作者:pigfu
    原文地址: https://blog.csdn.net/weixin_38597669/article/details/89405337
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞