栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

InnoDB 中的行锁算法(记录锁、间隙锁与临键锁)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

InnoDB 中的行锁算法(记录锁、间隙锁与临键锁)

InnoDB存储引擎有3种行锁的算法,分别是:
  • Record Lock(记录锁): 单个记录上的锁
  • Gap Lock(间隙锁) : 锁定一个范围,但不包括记录本上
  • Next-Key Lock(临键锁): 锁定一个范围,并且锁定记录本身 ,左开右闭区间

加锁规则可以概括为:两个原则、两个优化和一个bug:

  • 原则1:加锁的基本单位是next-key lock,左开后闭
  • 原则2:查找过程中访问到的对象才会加锁
  • 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化成行锁
  • 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁
  • 1个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

看了这结论,应该可以解答一大部分问题,不过有一句非常非常重点的话需要关注:MySQL 后面的版本可能会改变加锁策略,所以这个规则只限于截止到现在的最新版本,即 5.x 系列<=5.7.24,8.0 系列 <=8.0.13

所以,以上的规则,对现在的版本并不一定适用,下面我以 MySQL 8.0.25 版本为例,进行多角度验证 next-key lock 加锁范围。

下面我以 MySQL 8.0.25 版本为例,进行多角度验证 next-key lock 加锁范围。 

环境准备

MySQL 版本:8.0.25

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

存储引擎:InnoDB


一、主键索引 创建表

a为主键索引、c为普通索引 

首先来验证主键索引的 next-key lock 的范围,对主键索引来说此时数据间隙如下:

1、 主键等值查询 —— 数据存在
select * from t where id = 10 for update;

 可以通过 data_locks 查看锁信息,SQL 如下:

select * from performance_schema.data_locks

咱们需要重点关注以下几个字段:

  • INDEX_NAME:锁定索引的名称

  • LOCK_TYPE:锁的类型,对于 InnoDB,允许的值为 RECORD 行级锁 和 TABLE 表级锁。

  • LOCK_MODE:锁的类型:S, X, IS, IX, and gap locks

  • LOCK_DATA:锁关联的数据,对于 InnoDB,当 LOCK_TYPE 是 RECORD(行锁),则显示值。当锁在主键索引上时,则值是锁定记录的主键值。当锁是在辅助索引上时,则显示辅助索引的值,并附加上主键值。

结果很明显,这里是对表添加了一个 IX 锁 并对主键索引 id = 10 的记录,添加了一个 X,REC_NOT_GAP 锁,表示只锁定了记录。

同样 for share 是对表添加了一个 IS 锁并对主键索引 id = 10 的记录,添加了一个 S 锁。

可以得出结论:

对主键等值加锁,且值存在时,会对表添加意向锁,同时会对主键索引添加行锁。

2、主键等值查询 —— 数据不存在 
select * from t where id = 11 for update;

如果是数据不存在的时候,会加什么锁呢?锁的范围又是什么?

在验证之前,分析一下数据的间隙。

  1. id = 11 是肯定不存在的。但是加了 for update,这时需要加 next-key lock,id = 11 所属区间为 (10,15] 的前开后闭区间;

  2. 因为是等值查询,不需要锁 id = 15 那条记录,next-key lock 会退化为间隙锁;

  3. 最终区间为 (10,15) 的前开后开区间。

使用 data_locks 分析一下锁信息:

看下锁的信息 X,GAP 表示加了间隙锁,其中 LOCK_DATA = 15,表示锁的是 主键索引 id = 15 之前的间隙。

此时在另一个 Session 执行 SQL,答案显而易见,是 id = 12 不可以插入,而 id = 15 是可以更新的。

可以得出结论,在数据不存在时,主键等值查询,会锁住该主键查询条件所在的间隙。

 3、主键范围查询(重点)
select * from t where id >= 10 and id < 11 for update;

分析得出下面结果:

  1. id >= 10 定位到 10 所在的区间 (10,+∞);

  2. 因为是 >= 存在等值判断,所以需要包含 10 这个值,变为 [10,+∞) 前闭后闭区间;

  3. id < 11 限定后续范围,则根据 11 判断下一个区间为 15 的前开后闭区间;

  4. 结合起来则是 [10,15]。(不完全正确)

先看下 data_locks

 可以看到除了表锁之外,还有 id = 10 的行锁(X,REC_NOT_GAP)以及主键索引 id = 15 之前的间隙锁(X,GAP)。

所以实际上 id = 15 是可以进行更新的。也就是说前开后闭区间出现了问题,个人认为应该是 id < 11 这个条件判断,导致不需要进行了锁 15 这个行锁。

结果验证也是正确的,id = 12 插入阻塞,id = 15 更新成功。

当范围的右侧是包含等值查询呢?

select * from t where id > 10 and id <= 15 for update;

来分析一下这个 SQL:

  1. id > 10 定位到 10 所在的区间 (10,+∞);

  2. id <= 15 定位是 (-∞, 15];

  3. 结合起来则是 (10,15]。

 同样先看一下 data_locks

 可以看出只添加了一个主键索引 id = 15 的 X 锁。

验证下 id = 15 是否可以更新?再验证 id = 16 是否可以插入?

事实证明是没有问题的!


结论一

  1. 加锁时,会先给表添加意向锁,IX 或 IS;

  2. 加锁是如果是多个范围,是分开加了多个锁,每个范围都有锁;(这个可以实践下 id < 20 的情况)

  3. 主键等值查询,数据存在时,会对该主键索引的值加行锁 X,REC_NOT_GAP;

  4. 主键等值查询,数据不存在时,会对查询条件主键值所在的间隙添加间隙锁 X,GAP;

结论二

通过使用 select * from performance_schema.data_locks; 和操作时间,可以看出 LOCK_MODE 和 LOCK_DATE 的关系:

LOCK_MODELOCK_DATA锁范围
X,REC_NOT_GAP1515 那条数据的行锁
X,GAP1515 那条数据之前的间隙,不包含 15
X1515 那条数据的间隙,包含 15
  1. LOCK_MODE = X 是左开右闭区间;

  2. X,GAP 是左开右开区间(间隙锁);

  3. X,REC_NOT_GAP 行锁。


二、非主键唯一索引

创建表

CREATE TABLE `t` (
  `id` int NOT NULL COMMENT '主键',
  `a` int DEFAULT NULL COMMENT '唯一索引',
  `c` int DEFAULT NULL COMMENT '普通索引',
  `d` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_a` (`a`),
   KEY `idx_c` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

数据库数据如下:

 数据库的字段 a 是唯一索引。

1、非主键唯一索引等值查询 —— 数据存在
 select * from t where a = 110 for update;

查看 data_locks

  1. 表锁 IX;

  2. 索引 uniq_a 上面加了 X,REC_NOT_GAP 行锁,其中 110, 10 表示是 a = 110 这行数据,后面的 10 是这行数据对应的主键;

  3. 主键 id = 10 上添加了 X,REC_NOT_GAP 行锁。

  4. 所以锁住唯一索引及对应的主键索引。

这里执行的 SQL 都是 select *,如果替换为 select id 呢?

select id from t where a = 110 for update;

并无什么区别。

把 for update 换成 for share,这时候区别来了:

select id from t where a = 110 for share;

 只有两条锁记录:表意向锁和 uniq_a 索引的 S,REC_NOT_GAP 锁。

很明显,for share 覆盖索引时,只是对自己的索引加锁。

update t set c = 2101 where id = 10;

update t set a = 1101 where id = 10;

update t set c = 2101 where a = 110;

执行结果很显然,第一个可以执行,而后两个是会阻塞的。 

所以,非主键唯一索引等值查询,数据存在,for update 是会在主键加锁的,而 for share 只有在走覆盖索引的情况下,会仅在自己索引上加锁。

2、非主键唯一索引等值查询 —— 数据不存在
select * from t where a = 111 for update;

分析这一条 SQL:

  1. 字段 a 具有唯一性,但是数据 a = 111 不存在,会一直查,查到 115 区间;

  2. 所以会加索引 uniq_a 和 主键索引的间隙锁。

非主键索引等值查询,数据不存在,相当于一个范围查询,仅仅会在非主键索引上加锁,加的还是间隙锁,左开右开区间;

 3、非主键唯一索引范围查询
select * from t where a >= 110 and a < 115 for update;

  1. 非主键唯一索引 (15,115)

  2. 对应的主键索引 10 也会加锁!

总结

在非主键唯一索引情况下:

  1. 非主键唯一索引等值查询,数据存在,for update 是会在主键加锁的,而 for share 只有在走覆盖索引的情况下,会仅在自己索引上加锁;

  2. 非主键索引等值查询,数据不存在,无论是否索引覆盖,相当于一个范围查询,仅仅会在非主键索引上加锁,加的还是间隙锁,前开后开区间;

  3. 在非主键唯一索引范围查询时,不是覆盖索引的时候,会对相应的范围加前开后闭区间,并且如果存在数据,会对对应的主键加行锁;

  4. 在非主键唯一索引范围查询时,如果是覆盖索引时,会对所有的后闭区间对应的主键,加行锁。


三、普通索引
CREATE TABLE `t` (
  `id` int NOT NULL COMMENT '主键',
  `a` int DEFAULT NULL COMMENT '唯一索引',
  `c` int DEFAULT NULL COMMENT '普通索引',
  `d` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_a` (`a`),
  KEY `idx_c` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

 1、普通索引等值查询 —— 数据存在
select * from t where c = 210 for update;

 直接分析 data_locks

  1. 表意向锁;

  2. 索引 idx_c 上添加了 210 区间的前开后闭锁;

  3. 索引 idx_c 上添加了 215 区间的间隙锁,LOCK_MODE 为 X,GAP;

  4. 主键上添加了 15 的行锁 ,LOCK_MODE 为 X,REC_NOT_GAP。

主要是因为普通索引不能唯一锁定一条记录,所以要锁定该字段的前后范围。

2、普通索引等值查询 —— 数据不存在 
select * from t where c = 211 for update;

 直接分析 data_locks

  1. 表意向锁;

  2. 索引 idx_c 上添加了 215 区间的间隙锁。

分析是因为数据不存在,只需要锁住 215 间隙就可以了,因为 215 和 210 肯定不属于这个范围。 

3、普通索引范围查询 
select * from t where c > 210 and c <= 215 for update;

  对于锁住 idx_c 索引的 215 的前开后闭区间是可以理解的,但是锁住了 220 就不太理解了,应该也是那个 bug 没有完全修复。


四、普通字段

对普通字段而言,无论是哪个查询,都需要扫描全部记录,所以这个锁直接加在了主键上,并且是锁住全部的区间。


总结

如果规律记不住,可以直接通过分析 data_locks 的信息,进行判断加锁范围。

select * from performance_schema.data_locks;
LOCK_MODELOCK_DATA锁范围
X,REC_NOT_GAP1515 那条数据的行锁
X,GAP1515 那条数据之前的间隙,不包含 15
X1515 那条数据的间隙,包含 15
  1. LOCK_MODE = X 是前开后闭区间;

  2. X,GAP 是前开后开区间(间隙锁);

  3. X,REC_NOT_GAP 行锁。

从而得出普通索引和普通字段的结论。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/327670.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号