浅谈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 05 MySQL
详解MySQL事务的隔离级别与MVCC
Apr 22 MySQL
MySQL 覆盖索引的优点
May 19 MySQL
MySQL触发器的使用
May 24 MySQL
mysql升级到5.7时,wordpress导数据报错1067的问题
May 27 MySQL
你知道哪几种MYSQL的连接查询
Jun 03 MySQL
MySQL 外键约束和表关系相关总结
Jun 20 MySQL
Mysql中where与on的区别及何时使用详析
Aug 04 MySQL
MySQL中varchar和char类型的区别
Nov 17 MySQL
mysql insert 存在即不插入语法说明
Mar 25 MySQL
MySQL表锁、行锁、排它锁及共享锁的使用详解
Apr 02 MySQL
前端传参数进行Mybatis调用mysql存储过程执行返回值详解
Aug 14 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
dedecms防止FCK乱格式化你的代码的修改方法
2007/03/17 PHP
PHP+Tidy-完美的XHTML纠错+过滤
2007/04/10 PHP
php 来访国内外IP判断代码并实现页面跳转
2009/12/18 PHP
PHP中的生成XML文件的4种方法分享
2012/10/06 PHP
twig模板常用语句实例小结
2016/02/04 PHP
php中bind_param()函数用法分析
2017/03/28 PHP
php使用curl获取header检测开启GZip压缩的方法
2018/08/15 PHP
PHP析构函数destruct与垃圾回收机制的讲解
2019/03/22 PHP
JQuery的一些小应用收集
2010/03/27 Javascript
用Javascript评估用户输入密码的强度(Knockout版)
2011/11/30 Javascript
js中的for如何实现foreach中的遍历
2014/05/31 Javascript
js实现网页右上角滑出会自动消失大幅广告的方法
2015/02/27 Javascript
JavaScript日期类型的一些用法介绍
2015/03/02 Javascript
js实现搜索框关键字智能匹配代码
2020/03/26 Javascript
javascript判断图片是否加载完成的方法推荐
2016/05/13 Javascript
JS中BOM相关知识点总结(必看篇)
2016/11/22 Javascript
微信小程序-详解数据缓存
2016/11/24 Javascript
AngularJS通过ng-route实现基本的路由功能实例详解
2016/12/13 Javascript
深入理解JavaScript继承的多种方式和优缺点
2017/05/12 Javascript
Nodejs中Express 常用中间件 body-parser 实现解析
2017/05/22 NodeJs
Angular通过angular-cli来搭建web前端项目的方法
2017/07/27 Javascript
JavaScript中的执行环境和作用域链
2020/09/04 Javascript
javascript全局自定义鼠标右键菜单
2020/12/08 Javascript
[04:29]DOTA2亚洲邀请赛小组赛第一日 TOP10精彩集锦
2015/02/01 DOTA
[56:48]FNATIC vs EG 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
用python生成1000个txt文件的方法
2018/10/25 Python
python 调用pyautogui 实时获取鼠标的位置、移动鼠标的方法
2019/08/27 Python
python SVD压缩图像的实现代码
2019/11/05 Python
python输出第n个默尼森数的实现示例
2020/03/08 Python
python实现模拟器爬取抖音评论数据的示例代码
2021/01/06 Python
一款纯css3实现的竖形二级导航的实例教程
2014/12/11 HTML / CSS
纯css3实现走马灯效果
2014/12/26 HTML / CSS
总裁岗位职责
2013/12/04 职场文书
中华魂演讲稿
2014/05/13 职场文书
有子女的离婚协议书怎么写(范本)
2014/09/29 职场文书
工程技术员岗位职责
2015/04/11 职场文书