Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >记录一次Mysql死锁排查过程

记录一次Mysql死锁排查过程

原创
作者头像
Java高级攻城狮
修改于 2019-05-07 10:11:46
修改于 2019-05-07 10:11:46
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

背景

以前接触到的数据库死锁,都是批量更新时加锁顺序不一致而导致的死锁,但是上周却遇到了一个很难理解的死锁。借着这个机会又重新学习了一下mysql的死锁知识以及常见的死锁场景。在多方调研以及和同事们的讨论下终于发现了这个死锁问题的成因,收获颇多。虽然是后端程序员,我们不需要像DBA一样深入地去分析与锁相关的源码,但是如果我们能够掌握基本的死锁排查方法,对我们的日常开发还是大有裨益的。

死锁起因

先介绍一下数据库和表情况,因为涉及到公司内部真实的数据,所以以下都做了模拟,不会影响具体的分析。

我们采用的是5.5版本的mysql数据库,事务隔离级别是默认的RR(Repeatable-Read),采用innodb引擎。假设存在test表:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `a` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8;

表的结构很简单,一个主键id,另一个唯一索引a。表里的数据如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select * from test;
+----+------+
| id | a    |
+----+------+
|  1 |    1 |
|  2 |    2 |
|  4 |    4 |
+----+------+
3 rows in set (0.00 sec)

出现死锁的操作如下:

步骤

事务1

事务2

1

begin

2

delete from test where a = 2;

3

begin

4

delete from test where a = 2; (事务1卡住)

5

提示出现死锁:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

insert into test (id, a) values (10, 2);

然后我们可以通过SHOW ENGINE INNODB STATUS;来查看死锁日志:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
------------------------
LATEST DETECTED DEADLOCK
------------------------
190219 13:35:31
*** (1) TRANSACTION:
TRANSACTION 2A8BD, ACTIVE 11 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 448218, OS thread handle 0x2abe5fb5d700, query id 18923238 renjun.fangcloud.net 121.41.41.92 root updating
delete from test where a = 2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BD lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 00000002; asc     ;;
 1: len 4; hex 00000002; asc     ;;

*** (2) TRANSACTION:
TRANSACTION 2A8BC, ACTIVE 18 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2
MySQL thread id 448217, OS thread handle 0x2abe5fd65700, query id 18923239 renjun.fangcloud.net 121.41.41.92 root update
insert into test (id,a) values (10,2)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 00000002; asc     ;;
 1: len 4; hex 00000002; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock mode S waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 00000002; asc     ;;
 1: len 4; hex 00000002; asc     ;;

*** WE ROLL BACK TRANSACTION (1)

分析

阅读死锁日志

遇到死锁,第一步就是阅读死锁日志。死锁日志通常分为两部分,上半部分说明了事务1在等待什么锁:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
190219 13:31:31
*** (1) TRANSACTION:
TRANSACTION 2A8BD, ACTIVE 11 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 448218, OS thread handle 0x2abe5fb5d700, query id 18923238 renjun.fangcloud.net 121.41.41.92 root updating
delete from test where a = 2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BD lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 00000002; asc     ;;
 1: len 4; hex 00000002; asc     ;;

从日志里我们可以看到事务1当前正在执行delete from test where a = 2,该条语句正在申请索引a的X锁,所以提示lock_mode X waiting

然后日志的下半部分说明了事务2当前持有的锁以及等待的锁:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
*** (2) TRANSACTION:
TRANSACTION 2A8BC, ACTIVE 18 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2
MySQL thread id 448217, OS thread handle 0x2abe5fd65700, query id 18923239 renjun.fangcloud.net 121.41.41.92 root update
insert into test (id,a) values (10,2)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 00000002; asc     ;;
 1: len 4; hex 00000002; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock mode S waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 4; hex 00000002; asc     ;;
 1: len 4; hex 00000002; asc     ;;

从日志的HOLDS THE LOCKS(S)块中我们可以看到事务2持有索引a的X锁,并且是记录锁(Record Lock)。该锁是通过事务2在步骤2执行的delete语句申请的。由于是RR隔离模式下的基于唯一索引的等值查询(Where a = 2),所以会申请一个记录锁,而非next-key锁。

从日志的WAITING FOR THIS LOCK TO BE GRANTED块中我们可以看到事务2正在申请S锁,也就是共享锁。该锁是insert into test (id,a) values (10,2)语句申请的。insert语句在普通情况下是会申请排他锁,也就是X锁,但是这里出现了S锁。这是因为a字段是一个唯一索引,所以insert语句会在插入前进行一次duplicate key的检查,为了使这次检查成功,需要申请S锁防止其他事务对a字段进行修改。

那么为什么该S锁会失败呢?这是对同一个字段的锁的申请是需要排队的。S锁前面还有一个未申请成功的X锁,所以S锁必须等待,所以形成了循环等待,死锁出现了。

通过阅读死锁日志,我们可以清楚地知道两个事务形成了怎样的循环等待,再加以分析,就可以逆向推断出循环等待的成因,也就是死锁形成的原因。

死锁形成流程图

为了让大家更好地理解死锁形成的原因,我们再通过表格的形式阐述死锁形成的流程:

步骤

事务1

事务2

1

begin

2

delete from test where a = 2; 执行成功,事务2占有a=2下的X锁,类型为记录锁。

3

begin

4

delete from test where a = 2; 事务1希望申请a=2下的X锁,但是由于事务2已经申请了一把X锁,两把X锁互斥,所以X锁申请进入锁请求队列。

5

出现死锁,事务1权重较小,所以被选择回滚(成为牺牲品)。

insert into test (id, a) values (10, 2); 由于a字段建立了唯一索引,所以需要申请S锁以便检查duplicate key,由于插入的a的值还是2,所以排在X锁后面。但是前面的X锁的申请只有在事务2commit或者rollback之后才能成功,此时形成了循环等待,死锁产生。

拓展

在排查死锁的过程中,有个同事还发现了上述场景会产生另一种死锁,该场景无法通过手工复现,只有高并发场景下才有可能复现。

该死锁对应的日志这里就不贴出了,与上一个死锁的核心差别是事务2等待的锁从S锁换成了X锁,也就是lock_mode X locks gap before rec insert intention waiting。我们还是通过表格来详细说明该死锁产生的流程:

步骤

事务1

事务2

1

begin

2

delete from test where a = 2; 执行成功,事务2占有a=2下的X锁,类型为记录锁。

3

begin

4

【insert第1阶段】insert into test (id, a) values (10, 2); 事务2申请S锁进行duplicate key进行检查。检查成功。

5

delete from test where a = 2; 事务1希望申请a=2下的X锁,但是由于事务2已经申请了一把X锁,两把X锁互斥,所以X锁申请进入锁请求队列。

6

出现死锁,事务1权重较小,所以被选择回滚(成为牺牲品)。

【insert第2阶段】insert into test (id, a) values (10, 2); 事务2开始插入数据,S锁升级为X锁,类型为insert intention。同理,X锁进入队列排队,形成循环等待,死锁产生。

总结

排查死锁时,首先需要根据死锁日志分析循环等待的场景,然后根据当前各个事务执行的SQL分析出加锁类型以及顺序,逆向推断出如何形成循环等待,这样就能找到死锁产生的原因了。

PS:上述分析都是基于经验的推断,希望其他小伙伴们能够指出当中的错误以及不足指出,谢谢!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程
遇到Mysql死锁问题,我们应该怎么排查分析呢?之前线上出现一个insert on duplicate死锁问题,本文将基于这个死锁问题,分享排查分析过程,希望对大家有帮助。
捡田螺的小男孩
2020/04/15
2.3K0
Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程
线上频出MySQL死锁问题!分享一下自己教科书般的排查和分析过程!
本文主要是讲过程与思路,从手上的日志来反推故障现场,最后模拟出事故现场。没有过度讲解理论的一些知识,主要是偏分析。
Guide哥
2020/07/28
6.3K2
MySQL 案例:Update 死锁详解
锁作为 MySQL 知识体系的主要部分之一,是每个 DBA 都需要学习和掌握的知识。锁保证了数据库在并发的场景下数据的一致性,同时锁冲突也是影响数据库性能的因素之一。而锁冲突中,有一类很经典的场景经常会拿出来讨论:死锁。最近刚好也遇到了一个典型的死锁案例,本文会基于这个案例,做一次详细的分析与拆解。
王文安@DBA
2020/10/26
15.5K4
MySQL 案例:Update 死锁详解
【京东技术双十一】记一次线上问题引发的对 Mysql 锁机制分析
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
京东技术
2023/11/14
4040
【京东技术双十一】记一次线上问题引发的对 Mysql 锁机制分析
MySQL死锁套路
我们用之前介绍过的源码分析方式,先来看下这两条语句分别加什么锁,然后分析死锁形成的过程。
数据和云
2019/05/28
2.4K0
MySQL案例:insert死锁与唯一索引
死锁是每个 MySQL DBA 都经常会遇到的问题,之前也写过关于死锁的详细解析。多数时候死锁容易在 update 中发生,且一般是涉及到二级索引。而本次遇到的问题是发生在 insert 上的死锁,与常规的场景不太一样,因此单独拿出来分析一下。
王文安@DBA
2022/06/07
4.6K1
MySQL案例:insert死锁与唯一索引
MySQL中RR模式下死锁一例
一、案例模拟二、死锁分析三、关于锁模式的变化四、关于LOCK_ORDINARY[next_key_lock]来历最后
老叶茶馆
2020/06/23
8670
[MYSQL] show engine innodb status中的死锁 分析
很久以前(也才2年)写过一个解析innodb_status的脚本. 看起来像那么回事, 其实就是做了个翻译和总结.
大大刺猬
2024/08/26
9980
[MYSQL] show engine innodb status中的死锁 分析
Mysql生产死锁问题定位
原因:org_code这个字段上存在索引,RC事务级别会产生间隙锁把相邻的位置锁住,多条消息过来多线程消费导致锁相互持有最终导致死锁
DH镔
2023/11/09
2940
MySQL在并发场景下的优化手段
对于数据库系统来说在多用户并发条件下提高并发性的同时又要保证数据的一致性一直是数据库系统追求的目标,既要满足大量并发访问的需求又必须保证在此条件下数据的安全,为了满足这一目标大多数数据库通过锁和事务机制来实现,MySQL数据库也不例外。尽管如此我们仍然会在业务开发过程中遇到各种各样的疑难问题,本文将以案例的方式演示常见的并发问题并分析解决思路。
Bug开发工程师
2018/07/23
1.2K0
MySQL在并发场景下的优化手段
MySQL RC模式insert update 可能死锁的情况
涉及的语句为 RC模式下 update根据主键更新和insert 其实这样的问题在RC模式下,要么是简单update问题,要么是insert造成的主键和唯一键检查唯一性时出现问题。 下面以主键问题为列子进行分析一下可能出现的情况。
星哥玩云
2022/08/13
1.2K0
一次死锁(Dead Lock)分析
当系统提示发生死锁Deadlock found when trying to get lock; try restarting transaction后,可在mysql控制台输入命令SHOW ENGINE INNODB STATUS \G,并找到LATEST DETECTED DEADLOCK部分分析一下,就能大概知道死锁的原因在哪里了。
天地一小儒
2022/12/28
6010
如何阅读MySQL死锁日志
客户在夜间批量执行数据处理时发生了死锁现象,是由不同的会话并发删除数据引起的,这个问题原因是比较简单,但想通过这个案例让大家熟悉如何去排查死锁问题,如何去阅读死锁日志这才是目的。通过模拟用户死锁现象后,死锁日志如下:
老叶茶馆
2022/12/02
2.3K0
MySQL 核心模块揭秘 | 39 期 | 死锁案例分析(1)插入意向锁
作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。
爱可生开源社区
2024/10/30
1610
MySQL 核心模块揭秘 | 39 期 | 死锁案例分析(1)插入意向锁
MySQL死锁分析
当业务并发比较高时,如果数据库访问设计得不合理,可能时不时就爆出一个死锁错误。业务上表现为一个偶现的失败。这种情况,有时候非常让人抓狂,感觉无从入手。这里就介绍一下对MySQL死锁的理解,并提出一个基于审计日志分析死锁的方法。
海伯利安
2021/01/25
1.7K0
MySQL死锁分析
一个MySQL死锁问题的复现
很久之前有一个同事问我一个关于死锁的问题,一直在拖这个事情,总算找了空来看看。 这个环境的事务隔离级别是RR,仔细看了下问题描述和背景,发现还真不是一块好啃的骨头。根据她的描述,是在两个会话并发对同一个表的不同行数据进行变更,两者是没有任何交集的,但是会抛出死锁问题。 这个问题我略做了改进,我改造成了两个SQL语句,最后再改进,就用一个shell脚本就能模拟出来了。 CREATE TABLE `t5` ( `id` int(11) NOT NULL AUTO_INCREMENT,
jeanron100
2018/03/21
1.6K0
这样分析一个死锁问题
之前也列举了几期的MySQL死锁问题,光有操作演练,还缺少一些自己的分析,所以我就打算补充一下。 首先对于死锁问题,我们分析的背景是基于MySQL事务隔离级别为RR,存储引擎为InnoDB,在MySQL 5.6,5.7版本均可复现问题。 怎么来分析一个死锁问题呢,我一直在琢磨这个问题,自己也总结了不少出现的场景,但是感觉还是有一些欠缺或者不完善的地方。那么我们就换一个思路来分析死锁问题,通过日志来反推死锁产生的可能场景,然后依次深入,扩展,这样一来,这个问题的分析就带有通过很多不确定性分析
jeanron100
2018/03/21
9150
这样分析一个死锁问题
手把手教你分析Mysql死锁问题
这时候可以用 select*frominformation_schema.innodb_locks;查看锁情况:
捡田螺的小男孩
2020/04/14
7280
故障分析 | MySQL死锁案例分析
死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。
爱可生开源社区
2023/01/05
9540
MySQL 核心模块揭秘 | 27 期 | 死锁(3)解决死锁
解决死锁最重要的事情,就是决定回滚死锁环中哪个事务,也就是选择哪个事务作为死锁受害事务。
爱可生开源社区
2024/09/14
1250
MySQL 核心模块揭秘 | 27 期 | 死锁(3)解决死锁
相关推荐
Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验