看来,MySQL next-key lock 的 bug 并没有被修复!

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 在上一篇文章《MySQL next-key lock 加锁范围是什么?》中已经介绍了主键索引的加锁范围,现在来回顾一下

前言


在上一篇文章《MySQL next-key lock 加锁范围是什么?》中已经介绍了主键索引的加锁范围,现在来回顾一下:

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

这篇文章会对非主键唯一索引进行操作实践。


数据库表数据

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 是唯一索引。


非主键唯一索引


非主键唯一索引等值查询 —— 数据存在

mysql> begin; select * from t where a = 110 for update;

分析一下这条 SQL:

  1. select * 查询条件肯定会回表;
  2. 唯一索引,所以定位到数据后不需要继续查询;
  3. 猜测是锁住唯一索引及对应的主键索引。


查看 data_locks

网络异常,图片无法展示
|

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

一切和分析的一样。

如果把 for update 换成 for share,其实也是相同,在主键和唯一索引上都加了锁。

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

mysql> begin; select id from t where a = 110 for update;


分析一下这条 SQL:

  1. select id 查询,满足索引覆盖,不会回表;
  2. 唯一索引,所以定位到数据后不需要继续查询;
  3. 那这里是锁主键索引还是两个都锁?

网络异常,图片无法展示
|


所以看出并无什么区别。

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

网络异常,图片无法展示
|


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

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

update t set c = 2101 where id = 10;


这时候使用主键更新 c 是否能更新? 那下面两个 SQL 呢?

update t set a = 1101 where id = 10;
update t set c = 2101 where a = 110;

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

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


非主键唯一索引等值查询 —— 数据不存在

mysql> begin; select * from t where a = 111 for update;


分析这一条 SQL:

网络异常,图片无法展示
|

  1. 首先加了 for update,肯定会在 索引 uniq_a 和 主键索引上都加上锁;
  2. 字段 a 具有唯一性,但是数据 a = 111 不存在,会一直查,查到 115 区间;
  3. 所以会加索引 uniq_a 和 主键索引的间隙锁。(并不对 )

网络异常,图片无法展示
|


事实证明,分析结果不正确。

网络异常,图片无法展示
|

并且我执行 update t set c = 2101 where id = 15; 也过了。

所以是不是可以理解为,非主键索引等值查询,数据不存在,相当于一个范围查询,仅仅会在非主键索引上加锁,加的还是间隙锁,前开后开区间;

如果此时走索引覆盖呢? 其实结果也是相同的。


非主键唯一索引范围查询

mysql> begin; select * from t where a >= 110 and a < 115 for update;

分析 SQL

网络异常,图片无法展示
|

  1. a >= 110 and a < 115,非主键唯一索引 [110,115),肯定是要加锁的;
  2. 对应的主键索引 10 应该也会加锁!


事实证明,又一次是错误的!

网络异常,图片无法展示
|


分析 data_locks:

  1. 怎么会对非主键唯一索引的 110 加了锁?LOCK_MODE 还是 X,如果加了行锁我还能理解。
  2. 怎么会对非主键唯一索引的 115 加了锁?

网络异常,图片无法展示
|


很明显 110 和 115 之前的间隙以及它们自身的记录都被锁住了。

经过一番分析,难道是因为前开后闭


脑袋炸裂呀,完全和主键索引的 next-key lock 加锁范围不同,人家 sql 是什么就锁什么。

有小伙伴知道原因可以告诉我。

如果我把 sql 改成下面的这种呢?

mysql> begin; select *  from t where a > 110 and a < 114 for update;

网络异常,图片无法展示
|


诶???

奇了怪了!

我唯一能想到的原因就是前开后闭了。 因为 a >= 10 中的等于是属于上一个区间的,所以需要锁住上一个区间。

网络异常,图片无法展示
|


我只能说懵逼三连了!!!

其实还是有结论的:

在非主键唯一索引范围查询时,会对相应的范围加前开后闭区间,并且如果存在数据,会对对应的主键加行锁。

这时候如果走覆盖索引呢??

mysql> begin; select id from t where a >= 110 and a < 115 for update;


按照刚才的思路,前开后闭:

  1. 锁住主键索引 110 的区间,115 的区间
  2. 锁住主键 10 的行锁

事实又错了!

网络异常,图片无法展示
|


还锁住了主键 15 的行锁。

网络异常,图片无法展示
|


把等号去掉 15 是锁住的。

感觉脑袋完全不够用啊。重点是我没有理解怎么主键还是前开后开,这里就前开后闭了?


难道我在这里试试那个 bug?

网络异常,图片无法展示
|

啪啪打脸啊!

之前还说这个 bug 在 8.0.18 被修复了,并优化成了前开后开区间,这直接打脸,明摆着没有修复。

我只是操作 a > 100 and a <= 115 for update; 竟然把 120 给我锁住了,不就是 next-key 的 bug。

尝试一下 sql

网络异常,图片无法展示
|

很明显~ 这个 bug 在非主键唯一索引上,并没有修复!!!


总结


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

  1. 非主键唯一索引等值查询,数据存在,for update 是会在主键加锁的,而 for share 只有在走覆盖索引的情况下,会仅在自己索引上加锁;
  2. 非主键索引等值查询,数据不存在,无论是否索引覆盖,相当于一个范围查询,仅仅会在非主键索引上加锁,加的还是间隙锁,前开后开区间;
  3. 在非主键唯一索引范围查询时,不是覆盖索引的时候,会对相应的范围加前开后闭区间,并且如果存在数据,会对对应的主键加行锁;
  4. 在非主键唯一索引范围查询时,如果是覆盖索引时,会对所有的后闭区间对应的主键,加行锁。

实践完本文的所有操作,个人处于有些懵逼的状态。我使用的版本是 8.0.25

  1. 主键不是前开后闭,而非主键唯一索引看样子又很遵循前开后闭原则;
  2. next key 的 bug 在非主键唯一索引上,并没有被修复!

仔细一想,似乎又可以理解。

因为主键上的 next-key 的 bug 被修复了,同时优化了前开后闭区间为前开后开区间,而非主键唯一索引上这个 bug 没有被修复,所以没有优化。

嗯~ 大概就是这样吧!

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
10月前
|
SQL NoSQL AliSQL
MySQL RENAME hang问题分析与修复
问题现象:alter 过程 rename 文件一直失败,导致 crash。[Warning] InnoDB: Cannot rename file ./tradesupplymember/memberpropertyinfo_0024.ibd (space id 78102), retried 1000 times. There are either pending IOs or flushes 
363 0
MySQL RENAME hang问题分析与修复
|
11月前
|
SQL 关系型数据库 MySQL
Mysql Lock Wait
Mysql Lock Wait
154 0
|
7月前
|
关系型数据库 MySQL 数据库
MySQL报错:Lock wait timeout exceeded; try restarting transaction
MySQL报错:Lock wait timeout exceeded; try restarting transaction
|
7月前
|
SQL 关系型数据库 MySQL
MySQL报错:1205 Lock wait timeout exceeded; try restarting transaction处理
MySQL报错:1205 Lock wait timeout exceeded; try restarting transaction处理
|
8月前
|
关系型数据库 MySQL PHP
【Bug解决】Thinkphp5 PDO::__construct(): MySQL server has gone away解决办法
【Bug解决】Thinkphp5 PDO::__construct(): MySQL server has gone away解决办法
|
8月前
|
SQL 关系型数据库 MySQL
【MySQL异常】MySQL事务锁问题----lock wait timeout exceeded; try restarting transaction
【MySQL异常】MySQL事务锁问题----lock wait timeout exceeded; try restarting transaction
67 0
|
11月前
|
SQL 关系型数据库 MySQL
Mysql 异常:Lock wait timeout exceeded; try restarting transaction的解决办法
Mysql 异常:Lock wait timeout exceeded; try restarting transaction的解决办法
111 0
|
11月前
|
SQL 关系型数据库 MySQL
mysql Lock wait timeout exceeded; try restarting transaction解决方案
在测试程序时,打的断点怎么都跳不进去,console一直报 “Lock wait timeout exceeded; try restarting transaction”
115 0