你在MySQL中加了什么锁,导致死锁的?

Mysql 死锁案例3-间隙与意向插入冲突 (16, 16,16)换成(3,3,3)即插入数据行的下一条记录有,但c=5这个记录是事务1加的,与事务1的意向插入不冲突,也是隐式。同一个事务持有 gap lock (临键也一样)的前提下插入数据,会发生分裂:执行T1,加情况是普通索引c临键(0,5]和间隙(5,10)写成功,加id=5主键记录成功,共3把。执行T2,分裂,加情况成了(0,3)(3,5]和(5,10)以及主键id=5的记录。事务1持有(0,5)间隙,事务申请(0,5)意向插入阻塞。 阅读详情

前言

最近在看 了篇文章,讲的MySQL加了什么锁,导致死锁的,自己也跟着做了做,题目如下图:

​其实基础好的友友们,一眼就能看出会发生死锁,不懂的友友们也不要气馁,听我细细分析;

实验的 MySQL 版本是 8.0.21,;

如果友友们对 MySQL 的锁不太了解,又没有什么好的资源的话,推荐看看小林写的 MySQL 锁篇;

准备

新建一张 t_student 表,只有 id字段是主键字段,其他都是普通字段;

CREATE TABLE `t_student` ( `id` int NOT NULL, `no` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `age` int DEFAULT NULL, `score` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 复制代码

填充相关数据,最终,t_student 表的记录如下所示:

开始

在实验开始前,再次声明一下实验环境:

  • MySQL 版本:8.0.21

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

  • Navicat:12

启动两个事务,按照题目的 SQL 执行顺序,过程如下表格:

​可以看到,事务 A 和 事务 B 都在执行 insert 语句后,都陷入了等待状态(前提没有打开死锁检测),也就是发生了死锁,因为都在相互等待对方释放锁。

原因

为什么会发生死锁?

可以通过下面这条 SQL 语句来查看事务执行 SQL 的过程中,到底加了什么锁:

SELECT * FROM performance_schema.data_locks 复制代码

接下来,我们就一起来分析一下每一条 SQL 语句执行之后所加的锁;

Time 1 阶段

Time 1 阶段,事务 A 执行以下语句:

# 事务 A BEGIN > OK > 时间: 0s update t_student set score = 100 where id = 25 > Affected rows: 0 > 时间: 0s 复制代码

然后执行上述的锁查询语句,查看事务 A 此时加了什么锁,这里的话,有针对性的选择了几个输出,

SELECT `ENGINE`, `ENGINE_TRANSACTION_ID`, `LOCK_TYPE`, `LOCK_MODE`, `LOCK_STATUS`, `LOCK_DATA` FROM performance_schema.data_locks 复制代码

​从上图可以看到,共加了两个锁,分别是:

  • 表锁:X 类型的意向锁;

  • 行锁:X 类型的间隙锁;

这里我们重点关注行锁,图中 LOCK_TYPE 中的 RECORD 表示行级锁,而不是记录锁的意思,通过 LOCK_MODE 可以确认是 next-key 锁,还是间隙锁,还是记录锁:

  • 如果 LOCK_MODE 为 X,说明是 next-key 锁;

  • 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁;

  • 如果 LOCK_MODE 为 X, GAP,说明是间隙锁;

因此,此时事务 A 在主键索引(INDEX_NAME : PRIMARY)上加的是间隙锁,锁范围是 (20, 30)。

间隙锁的范围 (20, 30) ,是怎么确定的?

其实这是可以根据经验推断出来的,如果 LOCK_MODE 是 next-key 锁或者间隙锁,那么 LOCK_DATA 就表示锁的范围最右值,此次的事务 A 的 LOCK_DATA 是 30。

然后锁范围的最左值是 t_student 表中 id 为 30 的上一条记录的 id 值,即 20。因此,间隙锁的范围 (20, 30)。

​这里的话,我们可以再通过两个小实验来验证一下:

  • 在当前 t_student 表中的第一条记录前进行更新:

  • update t_student set score = 21 where id = 10; > Affected rows: 0 > 时间: 0s 复制代码

  • 通过对当前记录的锁分析,可以发现,加的依旧是 X 型间隙锁,同时,Lock_DATA 的值为15,即间隙锁的范围是 (10,15):

  • 在当前 t_student 表中的最后一条记录后进行更新:

  • update t_student set score = 21 where id = 99 > Affected rows: 0 > 时间: 0s 复制代码

  • 通过对当前记录的锁分析,可以发现,只加了一个 X 锁, LOCK_DATA 的值为 supremum pseudo-record,相当于比索引中所有值都大,但却不存在索引中,也可以视为最后一行之后的间隙锁;

  • For the last interval, the next-key lock locks the gap above the largest value in the index and the “supremum”pseudo-record having a value higher than any value actually in the index. The supremum is not a real index record, so, in effect, this next-key lock locks only the gap following the largest index value.

通过上述的两个小实验,不难发现,一般情况下,LOCK_DATA 的值代表的是右边的范围边界值;

Time 2 阶段

Time 2 阶段,事务 B 执行以下语句:

# 事务 B BEGIN > OK > 时间: 0s update t_student set score = 100 where id = 26 > Affected rows: 0 > 时间: 0s 复制代码

在执行锁查询语句,来看看事务 B 此时加了什么锁;

​从上图可以看到,行锁是 X 类型的间隙锁,间隙锁的范围是 (20, 30)。

事务 A 和 事务 B 的间隙锁范围都是一样的,为什么不会冲突?

两个事务的间隙锁之间是相互兼容的,不会产生冲突。

在 MySQL 官网上还有一段非常关键的描述:

Gap locks in InnoDB are “purely inhibitive”, which means that their only purpose is to prevent other transactions from Inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

间隙锁的意义只在于阻止区间被插入,因此是可以共存的。一个事务获取的间隙锁不会阻止另一个事务获取同一个间隙范围的间隙锁,共享(S型)和排他(X型)的间隙锁是没有区别的,他们相互不冲突,且功能相同。

Time 3 阶段

Time 3,事务 A 插入了一条记录:

​此时,事务 A 就陷入了等待状态。

再执行锁查询语句,来看看事务 A 此时加了什么锁导致了阻塞的发生;

​可以看到,事务 A 的状态为等待状态(LOCK_STATUS: WAITING),因为向事务 B 生成的间隙锁(范围 (20, 30))中插入了一条记录,所以事务 A 的插入操作生成了一个插入意向锁(LOCK_MODE:INSERT_INTENTION)。

插入意向锁是什么?

注意!插入意向锁名字里虽然有意向锁这三个字,但是它并不是意向锁,它属于行级锁,是一种特殊的间隙锁。

在 MySQL 的官方文档中有以下重要描述:

An Insert intention lock is a type of gap lock set by Insert operations prior to row Insertion. This lock signals the intent to Insert in such a way that multiple transactions Inserting into the same index gap need not wait for each other if they are not Inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to Insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with Insert intention locks prior to obtaining the exclusive lock on the Inserted row, but do not block each other because the rows are nonconflicting.

这段话表明尽管插入意向锁是一种特殊的间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作。

如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。

插入意向锁与间隙锁的另一个非常重要的差别是:尽管「插入意向锁」也属于间隙锁,但两个事务却不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。所以,插入意向锁和间隙锁之间是冲突的。

另外补充一点,插入意向锁的生成时机:

每插入一条新记录,都需要看一下待插入记录的下一条记录上是否已经被加了间隙锁,如果已加间隙锁,那 Insert 语句会被阻塞,并生成一个插入意向锁 。

Time 4 阶段

Time 4,事务 B 插入了一条记录:

insert into t_student(id, no, name, age,score) value (26, 'S0026', 'ace', 28, 90) > 1213 - Deadlock found when trying to get lock; try restarting transaction > 时间: 0.008s 复制代码

这里的话,因为做过优化,所以发生死锁时,直接被检测了出来,并且自动回滚了,有些可能是阻塞等待,进入了死锁状态;

通过锁分析也可以得知,事务 B 因为回滚,已经释放了间隙锁,之前事务 A 在 Time 3 阶段阻塞等待的 insert 语句也能够成功执行了:

所以为什么会发生死锁呢?

本次案例中,事务 A 和事务 B 在执行完后 update 语句后都持有范围为(20, 30)的间隙锁,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

总结

两个事务即使生成的间隙锁的范围是一样的,也不会发生冲突,因为间隙锁目的是为了防止其他事务插入数据,因此间隙锁与间隙锁之间是相互兼容的。

在执行插入语句时,如果插入的记录在其他事务持有间隙锁范围内,插入语句就会被阻塞,因为插入语句在碰到间隙锁时,会生成一个插入意向锁,然后插入意向锁和间隙锁之间是互斥的关系。

如果两个事务分别向对方持有的间隙锁范围内插入一条记录,而插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

MySQL死锁案例分:先delete,再insert,导致死锁 session1 已获取到IX,gap, 等待rec insert intention(插入意向锁), session1, session2 都在等待插入意向锁, 插入意向锁与gap冲突,双方都没有释放gap,又都在等待插入意向锁死锁发生。或独占,对记录加了排他之后,只有拥有该的事务可以读取和修改,其他事务都不可以读取和修改,并且同一时间只能有一个事务加写。加了读的记录,所有的事务都可以读取,但是不能修改,并且可同时有多个事务对记录加读 阅读详情

相关推荐

线上MySQL死锁分析——索引设置不当导致死锁

文章目录1. 背景2. MySQL InnoDB的机制2.1 MySQL中的类型2.2 行的加规则2.3 死锁检测机制3. 本文案例分析3.1 分析InnoDB status日志3.2 Explain 死锁SQL、查看表的索引信息3.3 查看业务代码3.4 实验验证猜想3.5 问题解决4. 如何预防死锁参考 1. 背景 9月4号负责的系统接入了在线诊断分析平台,其中的运行时Java异常追踪工具能够捕获并上报线上异常。接入后发现系统会频繁的产生org.springframework.dao.Deadl

萝卜头柯克船长的博客 3144

MySQL 原理通过 6 个死锁案例,让你彻底理解 MySQL 机制,死锁的原因!

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

2401_83916204的博客 972

MySQL死锁死锁检测

MySQL死锁是指两个或多个事务在互相等待对方释放资源,导致无法继续执行的情况。

u012002125的博客 1780

MySQL并发插入导致死锁

网关莫名奇妙的重试引发mysql并发插入,居然会导致了这样的神奇加逻辑,导致线上出现死锁问题。。。

云舒编程的博客 2408

MySQL的总结 和 一次插入意向锁死锁还原分析

Deadlock found when trying to get lock; try restarting transaction Insert Intention Locks LOCK_INSERT_INTENTION LATEST DETECTED DEADLOCK MySQL的总结 和 一次插入意向锁死锁还原分析总结为什么要加?有什么?常见的三种常见的五种模式的兼容性如何分析死锁已经发生死锁正在阻塞中,等待释放。记一次死锁还原分析死锁总结

demo 2788

MySql 加了什么导致死锁

两个事务即使生成的间隙的范围是一样的,也不会发生冲突,因为间隙目的是为了防止其他事务插数据,因此间隙与间隙之间是相互兼容的.因为插入语句在碰到间隙时,会生成一个插入意向锁,然后插入意向锁和间隙之间是互斥的关系。如果两个事务分别向对方持有的间隙范围内插入一条记录,而插入操作为了获取到插入意向锁,在执行插入语句时,如果插入的记录在其他事务持有间隙范围内,插入语句就会被阻塞,

chichengfeng1的博客 339

Mysql机制之行、表死锁

乐观用的最多的就是数据的版本记录来体现 version ,其实就是一个标识。 例如:update test set a=a-1 where id=100 and a> 0; 对应的version就是a字段,并不一定非得要求有一个字段叫做version,要求的是有这个字段,同时当满足这个条件的时候才会触发

点滴记忆,分享成长之路 4268

MySQL死锁问题如何分析&表后查看死锁和去除死锁快速解决方法

(1) 遇到表快速解决办法   依次执行1-6步,运行第6步生成的语句即可。   如果特别着急,运行 1 2 6 步 以及第6步生成的kill语句 即可。 1. 第1步 查看表是否在使用。 show open tables where in_use > 0 ; 如果查询结果为空。则证明表没有在使用。结束。 mysql> show open tables where in_use > 0 ; Empty set (0.00 sec) 如果查..

赵英超的博客 2万+

MySQL死锁了怎么办(死锁的产生及解决方案),死锁的案例,死锁的排查,死锁的解决,如何避免死锁的发生

死锁是指2+的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

weixin_44797327的博客 1万+

MySQL中的以及死锁问题

利用它的唯一性来保证订单表不会出现重复的订单,不过有一点不好的地方就是在我们插入一个已经存在的订单记录时就会抛出异常。:Record Lock + Gap Lock 的组合,定一个范围,并且定记录本身。,这样在备份数据库期间,不会因为数据的更新,而出现备份文件的数据与预期的不一样。,直到拥有间隙的那个事务提交为止(释放间隙的时刻),在此期间会生成一个。,表明有事务想在某个区间插入新记录,但是现在处于等待状态。:一个事务在插入一条记录的时候,需要判断插入位置。:定一个范围,但是不包含记录本身;

qq_70186387的博客 1691

MySQL篇】MySQL死锁问题以及解决方案

mysql死锁及其解决办法

weixin_56738054的博客 1万+

MySQL相关——机制、表、行死锁优化等

MySQL不同的存储引擎支持不同的机制,所有存储引擎都以自己的方式显现了机制,服务器层完全不了解存储引擎中的实现。默认情况下表和行都是自动获得的,不需要额外的命令,但某些情况下用户需要明确的进行行或进行事务的控制,以便确保整个事务的完整性,这样就需要使用事务控制和定语句来完成。默认情况下,写比读具有更高的优先级:当一个释放时,这个会优先给写队列中等候的获取请求,然后再给读队列中等候的获取请求。对于普通的select语句,InnoDB不会加任何

weixin_43816711的博客 5119

MySQL的行死锁

简介 MySQL的行是在引擎层由各个引擎自己实现的,并不是所有引擎都支持行,比如MylSAM就不支持行,不支持行意味着他的并发控制只能使用表,这样就会很影响业务的并发度,因为它的范围更大,InnoDB支持行的,这也是MyISAM被InnoDB替代的原因之一。 行是指针对数据库中表中每行数据的,当一个事务A对这行数据进行更新时,另一个事务B也要对这行数据更新,那么必须要等事务A的操作完成过后才可以更新。 俩阶段协议 如图所示:事务B的update语句会被堵塞,直到事务A执行comm

lisensss的博客 2745

MySQL】深入理解MySQL机制:从行、表死锁

想象一个银行转账场景:用户A向用户B转账100元。事务1:A账户减100元事务2:B账户加100元如果这两个操作同时被多个线程执行A扣了两次钱,但B只收到一次→数据不一致!(Lock)就是MySQL用来保证并发安全和数据一致性的核心机制。它确保在并发环境下,多个事务对同一数据的访问是有序、可控的。两个或多个事务互相等待对方释放导致所有事务都无法继续。sql深色版本-- 事务1-- 住A-- 等待B(被事务2住)-- 事务2-- 住B。

josuke66的博客 2296

MySQL 全解】:从行死锁,一次讲透所有面试考点

MySQL机制主要包括全局、表级和行级。全局使数据库只读,用于全库备份;表级分为表(限制读写)、元数据(保护表结构)和意向锁(标识行状态)。行级包含记录(S/X)、间隙(防止幻读)和临键(组合)。InnoDB行依赖索引,无索引时会全表,效果类似表。临键通过定记录和间隙解决幻读问题。死锁产生于事务互相等待,可通过统一访问顺序、缩小事务范围、合理索引等方式避免。理解机制对优化数据库并发性能至关重要。

2504_94294476的博客 1273

MySQL】【的前置知识】数据库有哪些?怎么看?的是什么?什么情况下会加什么?什么情况下会死锁死锁超时的处理一样么?

数据库中的,是一个很大的问题,从哪看起呢?该怎么看呢?所以在看之前,了解一些相关的前置知识,然后再去细看不同的场景下会加什么样的方便你快速理解。当然我们这里看的 引擎是 InnoDB 哈,那我们从以下几个问题看起:(1)数据库中的有哪些(怎么知道呢,网上的文章五花八门的各种都有,该如何下手)(2)去哪看,怎么看一个表加了哪些呢,或者某个操作加了哪些呢(3)的是什么(4)体验死锁

m0_56563273的博客 2199

详解MySQL死锁死锁检测

前言 上一篇文章中,介绍了 MySQL 的全局和表级,这篇文章讲解行MySQL 的行是在引擎层由各个引擎自己实现的,但并不是所有的引擎都支持行,比如 MyISAM 引擎就不支持行。不支持行意味着并发控制只能使用表,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。 InnoDB 是支持行的,这也是 MyISAM 被 InnoDB 替代的主要...

成长是一辈子的事 4122

Mysql死锁超时、慢sql总结

死锁超时、慢sql总结

抽象的螺旋 4764
上一篇: 谈一谈什么是 MySQL 中的 join 查询
下一篇: 这些13 种锁的实现方式你知道吗
Java_LingFeng
博客等级 码龄4年 501粉丝 259原创
评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值