mysql 间隙锁和临键锁原理

间隙锁产生的背景

备注: 本文使用的 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 a = 10 for update;
返回:(10,10,10)
T2 insert into tb value(2,10,11);
T3 select * from tb where a = 10 for update;
返回:(10,10,10),(2,10,11)
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 的锁,但是命中了主键上的锁范围,所以被主键索引上的锁阻塞

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1,Mysql X锁和S锁。 1)概念:利用数据库本身提供的锁机制(行级锁)来实现,锁定该行数据。trx_rows...
    沐兮_d64c阅读 12,165评论 0 3
  • 当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异...
    初来的雨天阅读 8,933评论 0 22
  • 间隙锁(Gap Lock)是Innodb在提交下为了解决幻读问题时引入的锁机制,(下面的所有案例没有特意强调都使用...
    小亮__阅读 108,608评论 28 72
  • 参考资料:极客时间《MySQL实战45讲》 1 基础架构 存储引擎层负责数据的存储和提取。其架构模式是插件式的,支...
    IM后海大鲨鱼阅读 4,130评论 0 0
  • 1.锁分类 当多个事务或进程访问同一个资源时,为了保证数据的一致性就会用到锁机制,在MySQL中锁有多种不同的分类...
    我叫王小事儿阅读 2,675评论 0 1