《MySQL实战45讲》——学习笔记21 “加锁规则、加锁案例、死锁示例“

news2024/11/18 7:35:55

紧接着上篇介绍可重复读隔离级别下的幻读问题及解决幻读引入的间隙锁和next-key lock的概念,本篇介绍了更新记录时加锁的规则,并用几个案例来说明规则;

通过学习本文,可以帮助通过加锁规则去判断语句的加锁范围;在业务需要使用可重复读隔离级别的时候,能够更细致地设计操作数据库的语句,解决幻读问题的同时,最大限度地提升系统并行处理事务的能力;

*更新数据时的加锁规则

这个规则有以下前提

1. MySQL后面的版本可能会改变加锁策略,所以这个规则只限于截止到现在的最新版本,即5.x系列<=5.7.24,8.0系列<=8.0.13;

2. 因为间隙锁在可重复读隔离级别下才有效,所以本篇文章接下来的描述,若没有特殊说明,默认是可重复读隔离级别;    

加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”;

原则1:加锁的基本单位是next-key lock;next-key lock是前开后闭区间;
原则2:查找过程中访问到的对象才会加锁;(对象指的是索引列,也包括主键id,而非整行记录;)
优化1:索引上的等值查询,给唯一索引加锁的时候,若值存在,next-key lock退化为行锁;
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁;
一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止;

为方便讨论加锁规则,这里用一个简单的表结构说明(同上一篇文章),建表和初始化语句如下:

# 3个字段的简单表:主键id、索引列c、非索引列d
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;

# 表中初始化6条数据
insert into t values
(0,0,0),
(5,5,5),
(10,10,10),
(15,15,15),
(20,20,20),
(25,25,25);

案例一:索引上等值查询(值不存在)——退化为间隙锁

条件

1. id为主键,聚簇索引,且唯一索引;
2. id=7的记录不存在;

分析

1. id=7不存在,根据原则1,加锁单位是next-key lock,sessionA加锁范围就是id-(5,10];
2. 同时根据优化2,这是一个等值查询(id=7),而最后一个值id=10不满足查询条件,next-key lock退化成间隙锁,因此最终加锁的范围是id-(5,10);
3. 间隙锁id-(5,10),对于id=8的插入会阻塞,对于id=10的更新不会阻塞;

补充:由于id天然的也是唯一索引,因此如果sessionA改为where id =5,则最终命中优化1,next-key lock退化为行锁,仅锁id=5这一列;

*案例二:非唯一索引等值查询 in share mode(值存在且覆盖索引)

这里sessionA要给索引c上c=5的这一行加上读锁;这个例子的现象看起来,是不是有一种“该锁的不锁,不该锁的乱锁”的感觉?

条件

1. c为二级索引,非唯一索引;
2. c=5这条记录存在;
3. 这条查询语句会走覆盖索引;

分析

1. 根据原则1,前开后闭,会给c-(0,5]加上next-key lock;
2. 要注意c是普通索引非唯一索引,因此仅访问c=5这一条记录是不能马上停下来的,需要向右遍历,查到c=10才放弃;根据原则2,访问到的都要加锁,因此要给c-(5,10]加next-key lock;
3. 但是同时这个符合优化2:等值判断,向右遍历,最后一个值不满足c=5这个等值条件,因此退化成间隙锁c-(5,10);因此最终加锁的范围是:间隙锁c-(0,5)、行锁c=5、间隙锁c-(5,10);
4. 注意:根据原则2,只有访问到的对象才会加锁,这个查询使用覆盖索引,并不需要访问主键索引,所以主键索引上没有加任何锁,这就是为什么sessionB的update更新语句可以执行完成;但sessionC要插入一个(7,7,7)的记录,就会被sessionA的间隙锁c-(5,10)锁住;如果这里的sessionB的语句改为 update t SET d = 100 WHERE c = 5; 则会被c=5的行锁锁住,尽管这条SQL更新的记录与id=5的记录时同一条;

需要注意,在这个例子中,lock in share mode只锁覆盖索引,但是如果是for update就不一样了,sessionB也会被阻塞;因为执行for update时,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁;这个场景下,如果要用lock in share mode来给行加读锁避免数据被更新的话,就必须得绕过覆盖索引的优化回一下表在查询字段中加入索引中不存在的字段;比如,将sessionA的查询语句改成select d from t where c=5 lock in share mode;这个例子说明,锁是加在索引列上的,也就是说这里的间隙锁(5,10)实际是c-(5,10)

*间隙锁实际上锁的是什么?

另外一个需要注意的点,判断某个值能否插入的时候,不能光看区间的两端的值是否在区间内,还需要结合索引树;例如,对于间隙锁c-(5,10),这里如果插入一条(9,10,11),即c=10的记录,从区间上看,开区间(5,10)不包括10,但是依然无法插入;因为(9,10,11)这条记录,从c索引树上看,(c=10,id=9)会插在(c=10,id=10)的左边,因此会被c-(5,10)间隙锁锁住;而如果插入(11,10,11)这条记录,可以成功,因为从c索引树上看,(c=10,id=11)会插在(c=10,id=10)的右边,不会被c-(5,10)间隙锁锁住;

案例三:主键索引范围查询

先看2条SQL语句,他们的查询逻辑等价,但是加锁情况不一样

(1) select * from t where id=10 for update;
(2) select * from t where id>=10 and id<11 for update;

现在让sessionA执行第2个查询语句,来看看加锁效果;

条件

1. 查询条件使用id,id是主键索引,且为唯一索引;
2. 执行id>=10条件时,会执行id=10的等值判断,且为访问到的第一条数据;
3. 会执行id上的范围查询;

分析

1. 先执行id=10的等值查询,找到的第一个行id=10,因此next-key lock(5,10];根据优化1,主键(唯一索引)id上的等值条件,退化成行锁,只加了id=10这一行的行锁;
2. 接下来通过范围查找,根据bug1,唯一索引的范围查询会找到第一条不满足条件的行id=15,因此需要加next-key lock(10,15];因此最终加锁的范围是:行锁id=10和next-key lock id-(10,15];而next-key lock id-(10,15]包含间隙锁id-(10,15)和行锁id=15;

因此,插入id=13和更新id=15,都会被阻塞;

这里需要注意一点,首次sessionA定位查找id>=10的行的时候,是先当做id=10的等值查询来判断的,而向右扫描到id=15的时候,用的是范围查询判断;

案例四:非唯一索引范围查询

下面的案例与案例三对比,查询条件有唯一索引id换成了普通二级索引c;

条件

1. c是非唯一索引;
2. 使用了c的范围查询;
3. 使用了for update;
4. 使用select * ,因此查询会回表;

分析

1. 由于c是非唯一索引,并且这里使用了范围查询,因此优化1和优化2在这里都用不上;先查c=10,再往后找不满足条件的c=15的记录,即一共扫描了2条记录,产生的next-key lock分别是c-(5,10]和c-(10,15];
2. 由于回表,因此主键id也有间隙锁;
3. 使用了for update,因此扫描到的记录都会加上行锁;

因此,插入(8,8,8)被间隙锁阻塞,更新c=15的记录被next-key lock c-(10,15]中的c=15行锁阻塞;

案例五:唯一索引范围锁的"bug"

接下来再看一个关于加锁规则中bug的案例;

条件

1. id是主键,也是唯一索引;
2. 使用了id的范围查询;
3. 使用了for update;

分析

1. 由于id是唯一索引,按照条件查询,线找到id=15这条记录,对应next-key lock id-(10,15];
2. 根据bug1,唯一索引的范围查询会找到下一个不满足条件的记录,因此还会往后找到id=20这条记录,对应next-key lock id-(15,20];

因此,插入(16,16,16)被next-key lock id-(15,20]中的间隙锁(15,20)阻塞,更新id=20的记录被next-key lock id-(15,20]中的行锁id=20阻塞;

为什么认为是bug?

作为唯一索引,对于id<=15这个条件,其实已经找到了"唯一的一条"满足条件的记录,但是MySQL还是往后找到了"肯定不满足条件"的id=20这一行记录;

当前环境为MySQL 5.7.X,据说MySQL8.0.21版本已经修复;

案例六:limit 语句加锁

先给表中在插入一条id=10的记录,c索引结构如下;

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

下面对delete语句是否使用limit语句做一个对比;delete和for update的加锁逻辑类似,如果是走非主键索引的话,除了给当前索引加锁,还会顺便给主键索引加锁;

case(1)不使用limit语句的场景

分析:非唯一索引的等值查询,会找到满足c=10的两条记录(10,10,10)和(30,10,30),对应next-key lock c-(5,10];接下来要继续沿着索引c往下找,找到第一条不满足条件的记录(15,15,15),对应next-key lock c-(10,15];根据优化2,非唯一索引等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁c-(10,15);由于使用for update,因此主键id也会加锁;

因此,sessionB被间隙锁c-(10,15)阻塞,sessionC更新的id=15不在锁范围,可以正常执行;

case(2)使用limit语句的场景

这个例子里,sessionA的delete语句加了limit 2;表t里c=10的记录其实只有两条,因此加不加limit 2,删除的效果都是一样的,但是加锁的效果却不同;可以看到,sessionB的insert语句执行通过了,跟case(1)的结果不同;

这是因为,case(2)的delete语句明确加了limit 2的限制,因此在遍历到(c=10,id=30)这一行之后,满足条件的语句已经有两条,循环就结束了不会再往下去找id=15这条记录

因此,索引c上的加锁范围就变成了从(c=5,id=5)到(c=10,id=30)这个前开后闭区间,精确地间隙锁区间如下图所示:

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

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

案例七:一个死锁的例子——加 next-key lock是分2步的

前面的例子中,在分析的时候,是按照next-key lock的逻辑来分析的,因为这样分析比较方便;最后再看一个案例,目的是说明:next-key lock实际上是间隙锁和行锁加起来的结果,是分别加上的;

过程分析:

1. sessionA启动事务后执行查询语句加lock in share mode,在索引c上加了next-key lock c-(5,10]和间隙锁(10,15);
2. 按照规则和优化,sessionB的update语句也要在索引c上加next-key lock c-(5,10]和间隙锁(10,15),但是加锁过程中进入锁等待
3. 然后sessionA要再插入(8,8,8)这一行,被sessionB的间隙锁锁住;由于出现了死锁,InnoDB让sessionB回滚;

可能会有疑问,间隙锁加锁不是不会互相阻塞吗,阻塞的其实是插入操作?

确实如此,但是这里第2步,sessionB的"加next-key lock c-(5,10]"操作,实际上分成了两步,先是加(5,10)的间隙锁,加锁成功;然后加c=10的行锁,这时候才被锁住的;后面c-(10,15]的next-key lock还没来得及加,就被前面的id=10的行锁给锁住了;

也就是说,我们在分析加锁规则的时候可以用next-key lock来分析;但是要知道,具体执行的时候,是要分成间隙锁和行锁两段来执行的;

小结

1. 更新时的加锁规则及优化/BUG

原则1:加锁的基本单位是next-key lock;next-key lock是前开后闭区间;找到一条记录,再确定它的next-key lock区间;
原则2:查找过程中访问到的对象才会加锁;如果是覆盖索引,则不会对主键加锁;
优化1:索引上的等值查询,给唯一索引加锁的时候,若值存在,next-key lock退化为行锁;
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁;
一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止;也就是说哪怕类似>=的条件找到了这条=的记录,还会接着往下找;据说MySQL8.0.21版本已经修复;

2. 查询回表,例如条件虽然是二级索引,但 select * 需要回表,则主键id也会加锁;

3. 因为执行for update时,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁;

4. 间隙锁锁的是插入行为,不锁更新,除非还有行锁;

5. 在分析加锁规则的时候可以用next-key lock来分析;但是next-key lock加锁实际上分成了两步,例如next-key lock c-(5,10],先是加的间隙锁c-(5,10),加锁成功;然后加c=10的行锁;加间隙锁不会阻塞,但是加行锁可能阻塞;

6. 删除语句建议加上limit;delete和for update的加锁逻辑类似,明确加了limit N的限制,则仅扫描到指定数量N的记录,不会再接着往下找下一条不满足条件的记录,因此会减小锁的范围;

7. order by desc/asc 会影响查找的顺序,默认是顺序按照索引值从小到大查找,如果使用desc,则是从大往小找,加锁的范围是不同的;

补充:复现文中加锁场景的方法

使用的是HeidiSQL这款client工具,也可以使用MySQL workbench,刚开始尝试执行SQL时发现未提交的事务,另一个查询窗口可以查到;去搜索了下原因,是因为当前是一个连接,所以能读到当前事务未提交的更新;

正确的测试方法,是开多个客户端而不是一个客户端开多个查询窗口;执行语句时,逐行执行,即选择"执行选中的SQL语句",可以观察SQL执行日志来观察执行情况;

看本篇之前建议先看下上一篇文章:《MySQL实战45讲》——学习笔记20 “幻读、全表扫描的加锁方式、间隙锁、next-key lock“

下篇文章:待定

本章参考:21 | 为什么我只改一行的语句,锁这么多?-极客时间

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ABAP学习笔记之——第九章:ABAP对象

一、类 类(Class)可看做是对象的骨骼 (Template) 或者对象的类型。另外&#xff0c;类是抽象化对象的明细表。即可以说是制作对象的设计书。对象属性由描述对象的状态和行为的构成要素Component)决定。 比较结构化程序和面向对象程序 例&#xff1a; *比较结构化程序 和 面向…

Apache+PHP+MariaDB+MQTT重启指令

启动PHP服务 1.启动 systemctl start php-fpm.service Apache服务 1. 启动、终止、重启 systemctl start httpd.service #启动 systemctl stop httpd.service #停止 systemctl restart httpd.service #重启 2. 设置开机启动/关闭 systemctl enable httpd.service #开机…

Spring【日志文件的打印与输出】

Spring【日志文件的打印与输出】&#x1f34e;一.日志文件&#x1f352;1.1 日志的作用&#x1f352;1.2 怎样查看到日志信息&#x1f34e;二. 打印日志&#x1f352;2.1 自定义打印日志&#x1f352;2.2 ⽇志格式说明&#x1f34e;三.日志级别&#x1f352;3.1 日志级别的作用…

SpringMVC的收参方式?

第一种收取参数方式&#xff1a;数据类型收取参数 第二种收取参数的方式&#xff1a;实体类收取参数&#xff08;传参&#xff09; 第三种收取参数的方式&#xff1a;ajax 收取数组/集合 第四种收取参数的方式&#xff1a;ajax 接收json数据 第五种收取参数的方式&#xff1a;…

全网最牛最全Postman接口测试(五): postman设置接口关联,postman实现参数化

Postman(9): postman设置接口关联 postman设置接口关联 在实际的接口测试中&#xff0c;后一个接口经常需要用到前一个接口返回的结果&#xff0c; 从而让后一个接口能正常执行&#xff0c;这个过程的实现称为关联。 在postman中实现关联操作的步骤如下&#xff1a; 1、利用…

怎样避免软件测试中的漏测

什么是测试漏测&#xff1f; 测试漏测是指软件产品在测试结束后出现了在测试过程中没有被发现的bug。我们知道&#xff0c;漏测是每一个软件测试者最头疼的事&#xff0c;一旦出现漏测&#xff0c;首先给客户带来了非常不好的影响&#xff0c;特别是严重的功能性bug被漏测&…

程序员如何创造睡后收入呢?

程序员作为一个互联网行业的职业&#xff0c;在互联网当道的现在是非常吃香的。 作为程序员&#xff0c;一个基础工资的收入就已经是一笔不菲的收益了。 然而&#xff0c;钱总是不够花的&#xff0c;毕竟捂住口袋有点小难。 这时候&#xff0c;我们只好把视线转向另一边&…

算法面试题——删除链表后第N个节点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#xff…

C语言百日刷题第十三天

前言 今天是刷题第13天&#xff0c;放弃不难&#xff0c;但坚持一定很酷~ 临近期末&#xff0c;再刷一套模拟题 C语言百日刷题第十三天前言选择题判断题编程题选择题 1.若由定义int*p1,*p2,m5,n;以下赋值语句都正确的选项是&#xff08;&#xff09; A、p1&m;p2&n…

signintech/gopdf功能介绍与代码示例

signintech/gopdf功能介绍与代码示例 PDF相关常识以及signintech/gopdf功能概览 代码示例&#xff1a; 参考&#xff1a;https://gitee.com/ixuer/gopdf_example 代码示例包括&#xff1a; 设置页面配置&#xff0c;如宽度和高度。test/font: 添加ttf字体&#xff0c;并设置…

计算机毕业设计springboot+vue基本微信小程序的快递收发小程序

项目介绍 随着Internet的发展,人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化、网络化和电子化。本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用java语言技术、springboot框架和微信小程序来完成对系统的设计。整个开发过程首先对…

element-ui的Dropdown下拉菜单,传递多个参数

element-ui的Dropdown下拉菜单&#xff0c;传递多个参数 原因&#xff1a; Dropdown下拉菜单只允许我们传递一个参数 解决步骤: 在允许我们传递一个参数的哪里提前调用方法: 以前&#xff1a;<el-dropdown-item command"a"> 测试 </el-dropdown-item> 现…

一文揭密字节跳动薪资职级,资深测试开发居然能拿......

曾经的互联网是PC的时代&#xff0c;随着智能手机的普及&#xff0c;移动互联网开始飞速崛起。而字节跳动抓住了这波机遇&#xff0c;2015年&#xff0c;字节跳动全面加码短视频&#xff0c;从那以后&#xff0c;抖音成为了字节跳动用户、收入和估值的最大增长引擎。 自从字节逐…

Docker 基础和常用命令总结

一&#xff0c;Docker 简介 1.1&#xff0c;什么是 Docker1.2&#xff0c;Docker 与虚拟机的区别1.3&#xff0c;Docker 架构1.4&#xff0c;为什么用 Docker 二&#xff0c;Docker 基本概念 2.1&#xff0c;镜像2.2&#xff0c;容器2.3&#xff0c;仓库 三&#xff0c;Docker …

【记录】网站变灰色怎么实现?

最近&#xff0c;大家应该注意到了&#xff0c;各大网站和APP都变成灰色了。原因也很简单&#xff0c;一般在清明节、全国哀悼日、大地震的日子、以及一些影响力很大的伟人逝世或纪念日的时候使用&#xff0c;使用后的网站的网页变成灰色(黑白色)&#xff0c;以表示对逝者的悼念…

补贴来了 | 人社部:获得这些证书,国家有补贴

近期&#xff0c;人社部等3部门发布《关于做好失业保险稳岗位提技能防失业工作的通知》&#xff0c;通知提出拓宽技能提升补贴受益范围&#xff1a; 1.领取失业保险金人员取得职业资格证书或职业技能等级证书的&#xff0c; 可按照初级(五级)不超过1000元、中级(四级)不超过150…

流程引擎activiti太难?(看这篇就够了)

一、工作流介绍 1、概念 工作流(Workflow)&#xff0c;就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程&#xff0c;从而实现某个预期的业务目标&#xff0c;或者促使此目标的实现”。 …

[附源码]Python计算机毕业设计Djang基于vuejs的文创产品销售平台app

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

MPLS协议介绍

MPLS介绍 1.MPLS协议概述 MPLS根据自己的标签交换&#xff0c;需要给数据包先写上自己的标签&#xff0c;然后设备才能查看标签之后就转发&#xff0c;此标签是需要在原有的数据包的基础上加进去的&#xff0c;并没有将以前的包头删除&#xff0c;MPLS的标签加在了第二层帧的…

数据仓库规范建设指南

数据仓库规范建设指南 1.数仓公共开发规范 1.1 层次调用规范 稳定业务按照标准的数据流向进行开发&#xff0c;即ODS-> DWD-> DWS-> APP。 非稳定业务或探索性需求&#xff0c;可以遵循ODS-> DWD->APP或者ODS-> DWD-> DWM-> APP 两个模型数据流。 …