浅谈MySQL next-key lock 加锁范围


Posted in MySQL onJune 07, 2021

前言

某天,突然被问到 MySQL 的 next-key lock,我瞬间的反应就是:

浅谈MySQL next-key lock 加锁范围

这都是啥啥啥???

浅谈MySQL next-key lock 加锁范围

这一个截图我啥也看不出来呀?

仔细一看,好像似曾相识,这不是《MySQL 45 讲》里面的内容么?

什么是 next-key lock

A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.

官网的解释大概意思就是:next-key 锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。

先给自己来一串小问号???

  • 在主键、唯一索引、普通索引以及普通字段上加锁,是锁住了哪些索引?
  • 不同的查询条件,分别锁住了哪些范围的数据?
  • for share 和 for update 等值查询和范围查询的锁范围?
  • 当查询的等值不存在时,锁范围是什么?
  • 当查询条件分别是主键、唯一索引、普通索引时有什么区别?

浅谈MySQL next-key lock 加锁范围

既然啥都不懂,那只好从头开始操作实践一把了!

先看看看 《MySQL 45 讲》中丁奇老师的结论:

浅谈MySQL next-key lock 加锁范围

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

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

环境准备

MySQL 版本:8.0.25

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

存储引擎:InnoDB

mysql> select @@global.transaction_isolation,@@transaction_isolation\G
mysql> show create table t\G

浅谈MySQL next-key lock 加锁范围

如何使用 Docker 安装 MySQL,可以参考另一篇文章《使用 Docker 安装并连接 MySQL》

主键索引

首先来验证主键索引的 next-key lock 的范围

浅谈MySQL next-key lock 加锁范围

此时数据库的数据如图所示,对主键索引来说此时数据间隙如下:

浅谈MySQL next-key lock 加锁范围

主键等值查询 —— 数据存在

mysql> begin; select * from t where id = 10 for update;

这条 SQL,对 id = 10 进行加锁,可以先思考一下加了什么锁?锁住了什么数据?

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

# mysql> select * from performance_schema.data_locks;
mysql> select * from performance_schema.data_locks\G

具体字段含义可以参考 官方文档

浅谈MySQL next-key lock 加锁范围

结果主要包含引擎、库、表等信息,咱们需要重点关注以下几个字段:

  • 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 锁。

可以得出结论:

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

主键等值查询 —— 数据不存在

mysql> select * from t where id = 11 for update;

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

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

浅谈MySQL next-key lock 加锁范围

  • id = 11 是肯定不存在的。但是加了 for update,这时需要加 next-key lock,id = 11 所属区间为 (10,15] 的前开后闭区间;
  • 因为是等值查询,不需要锁 id = 15 那条记录,next-key lock 会退化为间隙锁;
  • 最终区间为 (10,15) 的前开后开区间。

使用 data_locks 分析一下锁信息:

浅谈MySQL next-key lock 加锁范围

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

浅谈MySQL next-key lock 加锁范围

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

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

主键范围查询(重点)

mysql> begin; select * from t where id >= 10 and id < 11 for update;

根据 《MySQL 45 讲》分析得出下面结果:

  • id >= 10 定位到 10 所在的区间 (10,+∞);
  • 因为是 >= 存在等值判断,所以需要包含 10 这个值,变为 [10,+∞) 前闭后闭区间;
  • id < 11 限定后续范围,则根据 11 判断下一个区间为 15 的前开后闭区间;
  • 结合起来则是 [10,15]。(不完全正确)

先看下 data_locks

浅谈MySQL next-key lock 加锁范围

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

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

浅谈MySQL next-key lock 加锁范围

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

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

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

来分析一下这个 SQL:

id > 10 定位到 10 所在的区间 (10,+∞);id <= 15 定位是 (-∞, 15];结合起来则是 (10,15]。

同样先看一下 data_locks

浅谈MySQL next-key lock 加锁范围

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

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

浅谈MySQL next-key lock 加锁范围

事实证明是没有问题的!

当然,这里有小伙伴会说,在 《MySQL 45 讲》 里面说这里有一个 bug,会锁住下一个 next-key。

浅谈MySQL next-key lock 加锁范围

事实证明,这个 bug 已经被修复了。修复版本为 MySQL 8.0.18。但是并没有完全修复!!!

参考链接地址:

https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-18.html

搜索关键字:Bug #29508068)

浅谈MySQL next-key lock 加锁范围

咱们可以分别用 8.0.17 进行复现一下:

浅谈MySQL next-key lock 加锁范围

在 8.0.17 中 id <= 15 会将 id = 20 这条数据也锁着,而在 8.0.25 版本中则不会。所以这个 bug 是被修复了的。

再来看下是前开后闭还是前开后开的问题,严谨一下,使用 8.0.17 和 8.0.18 做比较。

浅谈MySQL next-key lock 加锁范围

浅谈MySQL next-key lock 加锁范围

现在我估计大概率是在 8.0.18 版本修复 Bug #29508068 的时候,把这个前开后闭给优化成了前开后开了。

对比 data_locks 数据:

浅谈MySQL next-key lock 加锁范围

注意红色下划线部分,在 8.0.17 版本中 id < 17 时 LOCK_MODE 是 X,而在 8.0.25 版本中则是 X,GAP

总结

本文主要通过实际操作,对主键加锁时的 next-key lock 范围进行了验证,并查阅资料,对比版本得出不同的结论。

结论一:

  • 加锁时,会先给表添加意向锁,IX 或 IS;
  • 加锁是如果是多个范围,是分开加了多个锁,每个范围都有锁;(这个可以实践下 id < 20 的情况)
  • 主键等值查询,数据存在时,会对该主键索引的值加行锁 X,REC_NOT_GAP
  • 主键等值查询,数据不存在时,会对查询条件主键值所在的间隙添加间隙锁 X,GAP
  • 主键等值查询,范围查询时情况则比较复杂:
    • 8.0.17 版本是前开后闭,而 8.0.18 版本及以后,进行了优化,主键时判断不等,不会锁住后闭的区间。
    • 临界 <= 查询时,8.0.17 会锁住下一个 next-key 的前开后闭区间,而 8.0.18 及以后版本,修复了这个 bug。

优化后,导致后开,这个不知道是因为优化后,主键的区间会直接后开,还是因为是个 bug。具体小伙伴可以尝试一下。

结论二

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

 

LOCK_MODE LOCK_DATA 锁范围
X,REC_NOT_GAP 15 15 那条数据的行锁
X,GAP 15 15 那条数据之前的间隙,不包含 15
X 15 15 那条数据的间隙,包含 15

LOCK_MODE = X 是前开后闭区间;X,GAP 是前开后开区间(间隙锁);X,REC_NOT_GAP 行锁。

基本已经摸清主键的 next-key lock 范围,注意版本使用的是 8.0.25。

疑问

  • 那唯一索引的 next-key lock 范围是什么?
  • 当索引覆盖时锁的范围和加锁的索引分别是什么?
  • 我为什么说这个 bug 没有完全修复,也是在非主键唯一索引中复现了这个 bug​。

文章篇幅有限,小伙伴可以先自己思考一下,尽量自己操作试一试,实践出真知。至于具体答案,那就需要下一篇文章进行验证并总结结论了。

到此这篇关于浅谈MySQL next-key lock 加锁范围 的文章就介绍到这了,更多相关MySQL next-key lock 加锁范围 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL创建索引需要了解的
Apr 08 MySQL
详解MySQL事务的隔离级别与MVCC
Apr 22 MySQL
MySQL sql_mode的使用详解
May 08 MySQL
MySQL 可扩展设计的基本原则
May 14 MySQL
MySQL为id选择合适的数据类型
Jun 07 MySQL
MySQL8.0.18配置多主一从
Jun 21 MySQL
mysql脏页是什么
Jul 26 MySQL
mysql 索引合并的使用
Aug 30 MySQL
面试被问select......for update会锁表还是锁行
Nov 11 MySQL
mysql insert 存在即不插入语法说明
Mar 25 MySQL
MySQL视图概念以及相关应用
Apr 19 MySQL
Mysql开启外网访问
May 15 MySQL
MySQL为id选择合适的数据类型
MySQL单表千万级数据处理的思路分享
Jun 05 #MySQL
MySQL 时间类型的选择
Jun 05 #MySQL
MySQL索引失效的典型案例
Jun 05 #MySQL
MySQL库表名大小写的选择
Jun 05 #MySQL
mysql 带多个条件的查询方式
Mysql 如何实现多张无关联表查询数据并分页
Jun 05 #MySQL
You might like
PHP导入Excel到MySQL的方法
2011/04/23 PHP
PHP输出多个元素的排列或组合的方法
2017/03/14 PHP
PHP空值检测函数与方法汇总
2017/11/19 PHP
IE中radio 或checkbox的checked属性初始状态下不能选中显示问题
2009/07/25 Javascript
javascript 函数速查表
2010/02/07 Javascript
jQuery中使用each处理json数据
2015/04/23 Javascript
JavaScript中switch语句的用法详解
2015/06/03 Javascript
对JavaScript的全文搜索实现相关度评分的功能的方法
2015/06/24 Javascript
解决js页面滚动效果scrollTop在FireFox与Chrome浏览器间的兼容问题的方法
2015/12/03 Javascript
AngularJS 使用$sce控制代码安全检查
2016/01/05 Javascript
快速掌握Node.js中setTimeout和setInterval的使用方法
2016/03/21 Javascript
浅谈JavaScript 浏览器对象
2016/06/03 Javascript
JS传值出现中文参数乱码的解决方法
2016/06/30 Javascript
React学习之事件绑定的几种方法对比
2017/09/24 Javascript
nodejs acl的用户权限管理详解
2018/03/14 NodeJs
对Vue beforeRouteEnter 的next执行时机详解
2018/08/25 Javascript
在小程序中推送模板消息的实现方法
2019/07/22 Javascript
微信小程序用户拒绝授权的处理方法详解
2019/09/20 Javascript
node.js中fs文件系统模块的使用方法实例详解
2020/02/13 Javascript
vue+element实现图片上传及裁剪功能
2020/06/29 Javascript
使用Node.js和Socket.IO扩展Django的实时处理功能
2015/04/20 Python
python中将正则过滤的内容输出写入到文件中的实例
2018/10/21 Python
利用python如何在前程无忧高效投递简历
2019/05/07 Python
python 实现GUI(图形用户界面)编程详解
2019/07/17 Python
python右对齐的实例方法
2020/07/05 Python
python能做哪些生活有趣的事情
2020/09/09 Python
解决margin 外边距合并问题
2019/07/03 HTML / CSS
Skechers越南官方网站:来自美国的运动休闲品牌
2021/02/22 全球购物
上海期货面试题
2014/01/31 面试题
音乐之声音乐广播稿
2014/09/10 职场文书
小学生五年级大队长竞选发言稿
2014/09/12 职场文书
新闻发布会活动策划方案
2014/09/15 职场文书
小学运动会报道稿
2014/10/04 职场文书
用python实现监控视频人数统计
2021/05/21 Python
常用的MongoDB查询语句的示例代码
2021/07/25 MongoDB
JAVA API 实用类 String详解
2021/10/05 Java/Android