美文网首页数据库mysql
mysql 间隙锁和临键锁原理

mysql 间隙锁和临键锁原理

作者: java后端领域 | 来源:发表于2020-03-21 15:40 被阅读0次

    间隙锁产生的背景

    备注: 本文使用的 MySQL 版本是: 8.0.13

    隔离级别:可重复读(RR)

    存储引擎:Innodb

    以下面的表为例子进行说明

    CREATE TABLE `tb` ( `id` int(11) NOT NULL, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `a` (`a`)) ENGINE=InnoDB;
    insert into tb values(0,0,0),(10,10,10),(20,20,20),(30,30,30),(40,40,40),(50,50,50);
    

    间隙锁的产生来自于 InnboDB 引擎在可重复读的级别基础上执行当前读时出现的幻读问题。下面来分析一下幻读的例子,假如没有间隙锁的话,那么会出现下面的现象:

    sessionA sessionB
    T1 begin;
    select * from tb where b = 10 for update;
    返回:(10,10,10)
    T2 insert into tb value(2,2,10);
    T3 select * from tb where b = 10 for update;
    返回:(10,10,10),(2,2,10)
    T4 commit;

    如上表如示,是基于没有间隙锁的假设,sessionA 事务内执行两次相同的当前读返回的数据不一样,出现幻读的现象。因为(2,2,10)这条记录在原本的数据并不存在,行锁就锁不住,因此诞生间隙锁。

    间隙锁加锁规则

    间隙锁和行锁合称 next-key lock,每一个 next-keylock 是前开后闭区间,如: (0,10]

    • 原则 1:加锁的基本单位是 next-key lock。

    • 原则 2:加锁是基于索引的,查找过程中访问到的对象才会加锁

    • 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁

    • 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

    分析上面例子

    select * from tb where b = 10 for update;

    1. 加锁的基于索引的
      因为 b 不是索引,索引走的是主键索引,加锁就是加在主键索引上

    2. 加锁范围

      遍历主键索引,发现 b=10 时,需要在前后记录之间加锁,所以在前一主键记录(0,0,0)和本记录之间加锁(0,10],在后一条主键记录(20,20)和本记录之间加锁 (10,20]。然后继续向右遍历,判断 b=20,!=10,满足优化 2 规则,next-key lock 退化为间隙锁,变成(10,20)。同时 b=10 加了行锁,汇总锁范围: (0,20),针对 id 主键

    3. 间隙锁与“往这个间隙插入操作”冲突

      因为上面主键 id 锁的范围是(0,20),因此插入(2,2,10)中 主键 2 属于锁范围,因此阻塞

      注意: 线程之间的间隙锁是不冲突的

    查询条件走二级索引例子

    sessionA sessionB
    T1 begin;
    select id from tb where a = 20 for update;
    T2 场景 1:insert into tb values(5,11,11);//阻塞
    场景 2: insert into tb values(15,10,8);//阻塞
    场景 3:insert into tb values(17,9,1);//成功
    场景 4: insert into tb values(25,35,88);//成功
    T3 commit;

    首先看看sessionA的执行计划,发现用到覆盖索引

    explain select id from tb where a = 20 for update;

    1. 因为条件走了 a 索引,查询字段 id 在索引 a中,用到的覆盖索引,索引在搜索过程中,只用到索引 a,所以只会在索引 a 中间隙锁,同时命中条件对应的主键也要加上行锁,所以主键 id=20 被加了行锁。

    2. 间隙锁加在索引 a 上的范围是(10,30),为了更加深度理解加锁范围,如下图:

    image.png

    场景 1:插入到索引 a 时,要插入是索引是(11,5),属于(a=10,id=10)和(a=30,id=30)之间的锁范围,所以阻塞

    场景 2、3、4 同理分析得出结论

    查询条件走主键索引例子

    sessionA sessionB
    T1 begin;
    select * from tb where a = 10 for update;
    T2 场景 1:insert into tb values(5,11,11);//阻塞
    场景 2: insert into tb values(15,10,8);//阻塞
    场景 3:insert into tb values(17,9,1);//阻塞(主键上的锁)
    场景 4: insert into tb values(25,35,88);//成功
    场景 5:insert into tb values(5,5,5);//阻塞(主键上的锁)
    T3 commit;

    本例子跟《查询条件走二级索引例子》区别在于 sessionA 是 select * ,因此需要回到主键索引查询所有字段,扫描了主键索引,所以也会在扫描到的索引进行加 next-key lock。该语句回表一次,扫描到是行是 id=10,所以加锁是(0,10],(10,20),因此 sessionA 一共加了锁是索引 a 的(10,30)和主键索引的(0,20)。

    • 场景 1,2 跟上一个例子一样命中索引 a 和主键索引的锁范围,阻塞
    • 场景 3 因为 17 属于主键索引(10,20)之间,所以被阻塞
    • 场景 4 不用索引 a 和主键索引的锁范围,所以成功。
    • 场景 5,没命中索引 a 的锁,但是命中了主键上的锁范围,所以被主键索引上的锁阻塞

    相关文章

      网友评论

        本文标题:mysql 间隙锁和临键锁原理

        本文链接:https://www.haomeiwen.com/subject/omxyyhtx.html