MySQL的加锁规则

学习了MySQL的锁后,知道其有这么多锁,那应该会有些疑惑,这么多锁,究竟我在写sql语句时候用到哪个锁的,什么情况是用什么锁的?在哪里查看该sql语句是用了哪些锁的呢?加锁的规则是什么呢?这篇文章就来解决这些疑惑。

MySQL的默认存储引擎是innodb,而我们常用的也是innodb,所以我们主要研究的是行级锁行级锁有行锁、间隙锁、临键锁

间隙锁和临键锁的唯一目是为了解决幻读现象。在可重复读(RR)隔离级别解决了幻读问题。那很明显在RR隔离级别下是使用了间隙锁和临键锁。

间隙锁和临键锁只有在可重复读隔离级别中才会存在,如果是在RC读已提交隔离级别下,是没有间隙锁的存在,只有行锁

所以,我们只讨论在RR隔离级别的innodb引擎表的锁。

这里测试环境mysql版本:

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36    |
+-----------+
1 row in set (0.00 sec)

--测试使用的表和表数据
CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;
 
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

下图中的规则是《MySQL45讲》专栏作者总结的。

我的理解:

从我使用的MySQL版本(MySQL8.0.36)测试来看,一个"bug" 是已经修复的了。

首先要清楚:加锁是对索引加锁的,不是对该行记录加锁的。

比如select id from t where c=10 lock in share mode;是对字段c的索引加临键锁(5,10],不是对主键索引加锁。

而比如select * from t where c=10 lock in share mode;对字段c的索引加临键锁(5,10],查询的是所有字段,就需要回表,那就会用到主键查询,那就是会访问到主键嘛, 也对主键索引加锁,这就是原则2所讲的。

优化1中所讲的,因为是唯一索引,那就只需要查询到该索引等值,就可以直接返回这个数据,因为是唯一的,不需要再往后查找了。只需要锁住那条数据即可,那next-key lock就可以退化为行锁。

所以是要区分唯一索引和非唯一索引

优化2中,等值查询的向后遍历是什么意思?因为可能这个等值查询的数据是不存在的。

比如 select id from t where id=9。而id=9这个数据是不存在的 ,那主键加锁就加在9的前后两个数据之间:id=5和id=10,所以是加临键锁(5,10]。而这个是等值查询,就继续在(5,10]从9往后遍历,到最后一个数据id=10,10不满足条件,那就不锁10,就可以退化成间隙锁(5,10)。

我个人认为:只需记住2点:

一个原则:加锁的基本单位是 next-key lock。加锁是对索引加锁的

一个bug:非唯一索引范围查询,该索引的临键锁不会有退化为间隙锁或记录锁

其他就通过常理来分析推理即可

为什么说是一个bug,是因为非唯一索引的范围查询不符合常理分析推理

那接下来结合我总结的,用具体例子来讲解。

案例一:唯一索引(主键)等值查询

红圈是sql语句输入的时间顺序。

分析过程:

  • 根据一个原则,加锁的基本单位是 next-key lock查询条件是id=7, 表中是没有id=7的数据,对主键加临键锁(5,10]。
  • 因为id=10是不符合条件的,所以可以退化成间隙锁(5,10)。

所以插入数据(8,8,8),即是id=8的数据被阻塞,因为id在(5,10)之间被锁住了,而更新id=10的数据是成功的。

上面的是我们的分析结果,那我们如何可以通过数据查看是否是加了这些表的呢?可以通过该语句

select * from performance_schema.data_locks\G;

来查看。

在执行了上图的第二步后,就执行该语句,可得

说明在主键索引加的是间隙锁(5,10),这是符合我们分析得到的结果的。 

案例二:非唯一索引等值锁

大家可能想到会话2应该是被阻塞的,而会话3是成功的,但结果却是相反的。

注意:普通的select...是不加锁的select ... lock in share mode 和 select ... for update才是加锁的lock in share mode是共享读锁,for update是独占写锁。

分析过程:

  • 根据一个原则,加锁的基本单位是 next-key lock,查询条件是c=5,给普通索引c加临键锁(0,5]。
  • 因为索引c不是唯一索引,那该值可能有多个,就需要继续往右查询,直到找到第一个不符合该值的值,那就是到了c=10,所以还需要给索引c加临键锁(5,10],而10是不符合条件的,所以退化成间隙锁(5,10)。这里非唯一索引的锁可以退化成间隙锁是因为这个查询不是范围查询(该查询条件是where c=5)。
  • 这里是select id,不需要回表,就不用给主键索引加锁。

 为什么索引c的临键锁(0,5]不退化成行锁?

因为 c字段是非唯一索引,不具有唯一性,所以非唯一索引的索引结构都是和主键连结在一起的,而performance_schema.data_locks表中LOCK_DATA中就需要加上主键值。 所以如果只加记录锁(记录锁无法防止插入,只能防止删除或者修改),就会导致其他事务插入一条 (c=5,id=4) 的记录,这样前后两次查询的结果集就不相同了,出现幻读现象。

主键索引上没加锁,所以 会话2的 update .... where id=5语句不被阻塞。

但是在会话3中insert values(7,7,7),即是c=7的数据,而索引c的锁范围是(0,10),所以c=7会被锁住。

查看加锁情况

在上图的步骤2后执行select * from performance_schema.data_locks\G。

 通过得知是对索引c加了临键锁(0,5]和间隙锁(5,10),总的即是锁住(0,10),符合我们分析得到的结果。

需要注意,在这个例子中,lock in share mode 只锁覆盖索引,但是如果是 for update 就不一样了。 执行 for update 时,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁。

另一种情况:

假如会话1中的select语句改成select * from t where c=5 lock in share mode,这个时候是查询所有字段,就会回表,那就需要主键索引,给主键索引加锁。

根据一个原则,加锁单位是临键锁,给主键加临键锁(0,5],而这里是等值查询,而且主键是唯一的,所以就只锁住id=5这行即可,即是退化成行锁。

案例三:主键索引范围锁

举例之前,你可以先思考一下这个问题:对于我们这个表 t,下面这两条查询语句,加锁范围相同吗?

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

id是int类型的,那这两条语句的逻辑是一样的。但是它们并不是等价的。

先来分析下第一条语句。

  • 根据原则1,加锁的单位是临键锁。查询条件是id=10,那给主键索引加临键锁(5,10]。
  • 主键是唯一的,那找到第一条id=10就会找到结果,不用再继续往右查找的。而表是有id=10这条数据的,所以不需要锁住(5,10],只需锁住10,这时就退化成行锁。

接着来看第二条语句

 分析过程:

  • 根据原则1,加锁的单位是临键锁。
  • 查询条件id>=10部分。id=10部分,主键加临键锁(5,10]。主键索引也是唯一索引,表也有id=10这条数据,所以退化成行锁,锁住id=10。>10部分,主键加临键锁(10,+∞]。
  • 而id<11部分,(id=11的下行数据是id=15),主键加临键锁(-∞,15],因为最后一个是15,不符合id<11,所以就退化成间隙锁(-∞,15)。这里我们要清楚,临键锁退化成间隙锁,是只能去掉临键锁的右区间,不是直接变成(-∞,11)。
  • 所以结合(10,+∞]和(-∞,15)和行锁id10,变成锁住[10,15)。即主键加的锁:行锁id=10,间隙锁(10,15)。

备注:在《MySQL45讲中》,该作者实验的步骤5的更新语句是被阻塞,而我使用的MySQL版本显示是更新成功,没有被锁的。作者的分析 是主键加锁是行锁 id=10 和 next-key lock(10,15]。这个和现在我的测试结果不符合的。可能是MySQL版本不同导致的,请大家注意。

查看加锁情况

在上图的步骤2后执行select * from performance_schema.data_locks\G。

 主键加锁:行锁id=10 和 间隙锁(10,15)符合我们分析得到的结果。

案例四:非唯一索引范围锁

该案例可以对照着案例三来看。与案例三不同的是,当前例子中查询语句的 where 条件中用的是字段 c(普通索引)。

这也是个困惑点,来验证我说的一个“bug”:非唯一索引范围查询,该索引的临键锁不会有退化为间隙锁或记录锁。这个bug主要是针对 查询条件是 非唯一索引<查询值 时候出现的,例如select ... where c<11。

步骤4和5更新的是同一行数据的,但是通过索引c更新的被堵住,说明加锁的是对索引加锁的,不是对行数据加锁。

分析过程:

  • 根据一个原则,加锁的基本单位是临键锁。查询条件是c>=10 and c<11。对普通索引c加临键锁。
  • c>=10部分。c=10,对普通索引c加临键锁(5,10]。案例二中讲明白了这个不能退化成为行锁。c>10部分,对索引c加临键锁(10,+∞]。即c>=10部分,索引c加锁范围是(5,+∞]。
  • c<11部分,索引c加临键锁(-∞,15],按照常理来分析推理的话,15不符合条件,所以退化成间隙锁(-∞,15),但是MySQL对此不是这样操作的,这个就是我说的一个bug,这里符合非唯一索引的范围查询,索引不能退化成间隙锁,所以是(-∞,15]。
  • 所以综合(5,+∞]和(-∞,15],索引c的锁:临键锁(5,10],临键锁(10,15]。
  • 因为是select *,所以需要回表。表有c=10这条数据,所以要锁住c=10对应的主键索引,即是锁住主键10,对主键加行锁。

insert语句中字段c=8,在索引c的锁范围(5,15]内,会被阻塞。而通过字段c=15来update也会被阻塞。通过id=15来update就不会阻塞,因为主键索引只加了行锁(id=10)。

查看加锁情况

在上图的步骤2后执行select * from performance_schema.data_locks\G。

 可以看到第二个锁的范围是(10,15],这是包含15的,但是按照 select * from t where c>=10 and c<11 for update;的逻辑那肯定是不锁住c=15的,但是该实现的锁就是锁住了c=15,这就不符合常理。

 更简单的,我们可以查看where c<11的加锁情况。

select * from t where c<11 for update;

--for update和lock in share mode 需要在事务中才能起效的,只是单单执行上面的语句是不起效的

begin;
select * from t where c<11 for update;

 select * from performance_schema.data_locks\G;查看锁情况。

按照常理分析,c=15是不符合条件的,所以就不应该锁住c=15的,但是结果却是临键锁,锁住了15。所以在对非唯一索引范围查询时有特别的做法, 不能退化成间隙锁。

案例五:非唯一索引上存在”等值”的例子

该例子,是为了更好地说明“间隙”这个概念。这里,我给表 t 插入一条新记录。

mysql> insert into t values(30,10,30);

新插入的这一行 c=10,也就是说现在表里有两个 c=10 的行。那么,这时候索引 c 上的间隙是什么状态了呢?而由于非唯一索引上包含主键的值,所以是不可能存在“相同”的两行的。

可以看到字段c是有两个10的,但是他们的主键是不一样的。所以这两个 c=10 的记录之间,也是有间隙的。

这次我们用 delete 语句来验证。注意,delete 语句加锁的逻辑,是加排他锁,其实跟 select … for update 是类似的。

分析过程:

  • 根据一个原则,加锁的基本单位是临键锁。条件是c=10,对普通索引c加临键锁(5,10]。
  • 而这不是唯一索引,所以需要继续完后搜索,直到搜索到c=15才停止,这里就加临键锁(10,15],因为15不符合条件的,所以可以退化成间隙锁(10,15)。所以对普通索引c加的锁:临键锁(5,10],间隙锁(10,15)。
  • 表中是有c=10这条数据的,那就需要回表,找到id=10和id=30符合条件,所以主键需要加两个行锁,锁id=10和id=30。

更新语句条件是c=15,没在普通索引c的加锁范围内,所以成功。而第三步的插入语句中插入了c=12的语句,所以被阻塞。

接着来看看步骤5,6是怎情况。步骤5插入数据(4,5,100)不阻塞,步骤6插入数据(6,5,100)被阻塞。100对应的字段是没有索引的,可以不用关注。

而明明普通索引c的加锁范围是(5,15),是不包括5的,为什么数据(6,5,100)会被阻塞的呢?

那明显是在主键上不同导致的插入阻塞的。

这时我们来看看普通索引c的加锁范围吧,中蓝色区域覆盖的部分就是加锁的范围。

为什么索引c的加锁范围变成这样呢?就是因为索引c不是唯一索引,可以有多个c=10,而c=10对应的多条数据的主键却是是不能相同的,所以加锁才会变成这样的。

从上图就可以看出来,在插入数据时候,id>5(比如6)就会被阻塞;id<5(比如4)就不被阻塞。

所以在插入数据时,能否插入成功,可能需要结合普通索引和主键索引来具体分析的。

查看加锁情况

在上面的步骤2后执行select * from performance_schema.data_locks\G;。

从这就可以看出,需要通过主键才能确定普通索引对应的数据。

案例六:limit 语句加锁

该例子对照案例五,在案例五的基础上limit 2。

这个例子里,左边会话 的 delete 语句加了 limit 2。而表 t 里 c=10 的记录其实只有两条,因此加不加 limit 2,删除的效果都是一样的,但是加锁的效果却不同。可以看到,右边会话 的 insert 语句执行通过了(案例5中也是插入(12,12,12),但是插入阻塞)。

分析过程:

  • 案例5中分析出来索引c的锁范围是(5,15),但是现在案例6中是limit2,其只需要两条数据。
  • 在锁(5,15)范围内找到了符合条件的2条数据,这里到了(c=10,id=30),但还没到锁范围的右边界,但是因为已经找齐数据了,就不再需要后面的锁了。这是很符合常理的,让所需的锁范围尽量小,而又不破坏业务需求。

可以看到,(c=10,id=30)之后的右边间隙并没有在加锁范围里,因此 insert 语句插入 c=12 是可以执行成功的。

这个例子对我们实践的指导意义就是,在删除数据的时候尽量加 limit。这样不仅可以控制删除数据的条数,让操作更安全,还可以减小加锁的范围。

案例七:非索引的查询

字段d是没有创建索引的

通过无索引字段d来进行查询,右边会话被阻塞。

查看加锁情况

 每一条记录的索引上都会加 next-key 锁,都是锁住主键,这样就相当于锁住的全表。

注意的是:不是加上表锁的。

不只是锁定读查询语句不加索引才会导致这种情况,update 和 delete 语句如果查询条件不加索引,那么由于扫描的方式是全表扫描,于是就会对每一条记录的索引上都会加 next-key 锁,这样就相当于锁住的全表。

总结

加锁的规则:

一个原则:加锁的基本单位是 next-key lock。加锁是对索引加锁的

一个bug:非唯一索引范围查询,该索引的临键锁不会有退化为间隙锁或记录锁

其他就通过常理来分析推理即可

查询的情况就那几种:

唯一索引等值查询

唯一索引范围查询

非唯一索引等值查询

非唯一索引范围查询

非索引查询

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/449419.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++初阶】第六站 : 模板初阶

前言&#xff1a; 本章知识点&#xff1a;泛型编程、函数模板、类模板 专栏&#xff1a; C初阶 目录 泛型编程 函数模板 1.函数模板概念 2.函数模板格式 3.函数模板的原理 4.函数模板的实例化 5.模板参数的匹配原则 类模板 类模板的定义格式 类模板的实例化 泛型编程 如何实现一…

Redis 的基本全局命令

前言 Redis 常用的有 5 种数据结构&#xff0c;字符串&#xff0c;列表&#xff0c;哈希表&#xff0c;集合&#xff0c;有序集合&#xff0c;每一种数据结构都有自己独特的命令&#xff0c;但也有些通用的全局命令&#xff0c;本文所提到的是最基本的命令&#xff0c;Redis 的…

linux查看文件内容cat,less,vi,vim

学习记录 目录 catlessvi vim cat 输出 FILE 文件的全部内容 $ cat [OPTION] FILE示例 输出 file.txt 的全部内容 $ cat file.txt查看 file1.txt 与 file2.txt 连接后的内容 $ cat file1.txt file2.txt为什么名字叫 cat&#xff1f; 当然和猫咪没有关系。 cat 这里是 co…

使用 IDEA 将本地jar上传到本地maven仓库

IDEA中的操作步骤 创建一个 Maven 运行配置 在开发工具的导航栏中&#xff0c;点击选择配置&#xff1a; 在配置界面点击左上角的加号&#xff0c;随后选择增加一个maven运行配置&#xff1a; 编辑 Maven 配置 上图中的含义&#xff1a; Name 对应的是本配置的名字、用处或功…

PyTorch搭建AlexNet训练集

本次项目是使用AlexNet实现5种花类的识别。 训练集搭建与LeNet大致代码差不多&#xff0c;但是也有许多新的内容和知识点。 1.导包&#xff0c;不必多说。 import torch import torch.nn as nn from torchvision import transforms, datasets, utils import matplotlib as p…

NFTScan | 03.04~03.10 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.03.04~ 2024.03.10 NFT Hot News 01/ 数据&#xff1a;比特币链上 NFT 过去 24 小时销售额超 3100 万美元 3 月 4 日&#xff0c;据数据显示&#xff0c;比特币链上 NFT 过去 24 小…

设计模式十:原型模式

文章目录 1、原型模式1.1 类创建过程1.2 浅拷贝1.3 深拷贝 2、示例2.1 简单形式2.2 复杂形式 3、spring中的原型模式3.1 ArrayList的原型模式3.2 spring中的原型模式 1、原型模式 原型模式就是从一个对象再创建另外一个可定制的对象&#xff0c; 而且不需要知道任何创建的细节。…

Vscode+QT+Python

参考链接&#xff1a;VSCodePyQt之Python界面编写_vscode编写图形化界面-CSDN博客 1.安装库 pip install PyQt5 pip install PyQt5-tools pip install qt5_applications 2.在VSCode里下载并安装PYQT Integration 3.配置pyqt integration 4.打开qt designer 在工程文件的空白…

python自动化之pytest框架以及数据驱动(第五天)

1.pytest框架需要遵循的规则 &#xff08;1&#xff09;.py 测试文件必须以test 开头(或者以 test结尾) &#xff08;2&#xff09;测试类必须以Test开头&#xff0c;并且不能有 init 方法 &#xff08;3&#xff09;测试方法必须以test 开头 &#xff08;4&#xff09;断言…

分享个好用的GPT网站

目录 一、背景 二、功能描述 1、写代码 2、联网查询 3、AI绘图 一、背景 我现在的开发工作都依靠ChatGPT&#xff0c;效率提升了好几倍。这样一来&#xff0c;我有更多时间来摸鱼&#xff0c;真是嘎嘎香~ ⭐⭐⭐点击直达 ⭐⭐⭐ 二、功能描述 1、写代码 import java.ut…

机器学习之分类回归模型(决策数、随机森林)

回归分析 回归分析属于监督学习方法的一种&#xff0c;主要用于预测连续型目标变量&#xff0c;可以预测、计算趋势以及确定变量之间的关系等。 Regession Evaluation Metrics 以下是一些最流行的回归评估指标: 平均绝对误差(MAE):目标变量的预测值与实际值之间的平均绝对差…

基于PHP+Amaze+JQuery的学习论坛的设计与实现1.99

摘 要 互联网教育服务是在互联网技术、通信技术、计算机技术不断发展融合的基础之上&#xff0c;人们在对以信息为基础的各种各样应用需求快速增长的激励之下&#xff0c;在现在社会信息化的水平日益提高前提之下&#xff0c;迅速发展起来的一种全新大众服务方式。 笔者拟设计…

前端食堂技术周刊第 115 期:Rolldown 正式开源、马斯克宣布 xAI 本周将开源 Grok、如何使用 Copilot 完成 50% 的日常工作?

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;手打柠檬茶 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看…

Docker的安装及镜像加速的配置

文章目录 一.切换到root二.卸载旧版docker三.配置docker的yum库四.安装Docker五.Docker的启动和验证六.配置Docker阿里云镜像加速(全程免费) 该文章文章演示在Linux系统中安装docker&#xff0c;Windows安装docker请参考以下文章 Windows系统中安装docker及镜像加速的配置 一…

基于android的物业管理系统的设计与实现19.8

目录 基于android的物业管理系统的设计与实现 3 摘 要 3 Android property managemengt system 5 Abstract 5 1 绪论 6 1.1 选题背景 6 1.2 课题研究现状 6 1.3 设计研究主要内容 7 1.4 系统主要设计思想 8 2 开发环境 8 2.1 Android系统的结构 8 图2-1 Android系统架构图 9 2…

kibana新增查看更新删除es中的数据

登录kibana&#xff0c;打开开发工具 写入数据 PUT test20240311/person/1 {"name": "张三","mobile":"13011111111" } 查询数据 GET /test20240311/person/_search {"query": {"term": {"mobile": {…

中科数安|公司办公终端、电脑文件数据 \ 资料防泄密系统

#中科数安# 中科数安是一家专注于信息安全技术与产品研发的高新技术企业&#xff0c;其提供的公司办公终端、电脑文件数据及资料防泄密系统&#xff08;也称为终端数据防泄漏系统或简称DLP系统&#xff09;主要服务于企业对内部敏感信息的安全管理需求。 www.weaem.com 该系统…

ffmpeg日记4001-原理介绍-视频切割原理

原理 打开输入---->打开输出---->根据输入来创建流---->拷贝流设置---->循环读帧---->判断时间点是否到达切割点&#xff0c;并做设置---->设置pts和dts---->写入---->善后 重点是pts和dts如何设置。参考《ffmpeg学习日记25-pts&#xff0c;dts概念的…

HBase非关系型数据库

HBase非关系型数据库 1 什么是HBase2 HBase的特点3 什么时候需要HBase4 HBase的数据模型5 HBase架构5.1 架构5.2 HBase如何列式储存 6 如何正确设计RowKey 1 什么是HBase HBase – Hadoop Database&#xff0c;是一个高可靠性、高性能、面向列、可伸缩、 实时读写的分布式数据…

Java并发编程: AQS

文章目录 一、前置知识二、什么是AQS三、使用AQS框架的锁和同步器1、ReentrantLock2、ReentrantReadWriteLock3、CountDownLatch4、CyclicBarrier5、Semaphore&#xff1a;信号量 四、锁和同步器的关系1、锁&#xff1a;面向锁的使用者2、同步器&#xff1a;面向锁的实现者 五、…