故障分析 | MySQL死锁案例分析

位流星云
• 阅读 696

作者:杨奇龙

网名“北在南方”,资深 DBA,主要负责数据库架构设计和运维平台开发工作,擅长数据库性能调优、故障诊断。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


一 背景

死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。

本次分享的死锁案例是 更新不存在的记录加上 X GAP lock 和 insert 的意向锁冲突。希望能够对想了解死锁的朋友有所帮助。

二 案例分析

2.1 业务逻辑

业务逻辑: 业务需要并发不同数据(insert+update),首先是更新记录,如果发现更新的 affect rows 为0,然后就执行插入,如果插入失败,再执行更新。因此存在并发的情况下,两个事务都执行了更新,affect rows 为0,然后有进行并发插入相同记录的情况。

2.2 环境说明

数据库版本 8.0.30

事务隔离级别 REPEATABLE-READ

create table dl(
id int auto_increment primary key,
c1 int not null ,
c2 int not null,
c3 int not null,
unique key uc1(c1),
unique key uc2(c2));

insert into dl(c1,c2,c3) values(2,0    ,2),(5,5,5);

2.3 测试用例

故障分析 | MySQL死锁案例分析

2.4 死锁日志

------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 1422661, ACTIVE 51 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 3 row lock(s), undo log entries 1
MySQL thread id 3149, OS thread handle 140261085611776, query id 3267 localhost msandbox update
insert into dl(c1,c2,c3) values(3,2,2)

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 50 page no 5 n bits 72 index uc1 of table `test`.`dl` trx id 1422661 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 50 page no 6 n bits 72 index uc2 of table `test`.`dl` trx id 1422661 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

*** (2) TRANSACTION:
TRANSACTION 1422664, ACTIVE 45 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 3152, OS thread handle 140261086668544, query id 3268 localhost msandbox update
insert into dl(c1,c2,c3) values(3,2,2)

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 50 page no 6 n bits 72 index uc2 of table `test`.`dl` trx id 1422664 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 50 page no 5 n bits 72 index uc1 of table `test`.`dl` trx id 1422664 lock mode S waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

*** WE ROLL BACK TRANSACTION (2)

2.5 死锁分析

  1. sess1 在 T3 时刻执行了更新,affect rows 为0,在c2的(2,5) 区间中加了X,GAP锁。

故障分析 | MySQL死锁案例分析

  1. sess2 在 T4 时刻执行了更新,affect rows 为0,同样在在c2的(0,3)区间中加了GAP锁 lock_mode X locks gap before rec,GAP 锁和GAP 锁相互兼容,因此 sess1 和 sess2 不会发生等待。

故障分析 | MySQL死锁案例分析

  1. sess1 在 T5 时刻执行了插入,由于插入记录c2 =2 的时候需要申请插入意向锁(insert intention lock),而insert intention lock锁和已存在的 (0,3) 的 lock_mode X locks gap before rec insert intention waiting是冲突的,也就是sess1 需要等待sess2 在 T4 持有的GAP X锁,发生了等待.

故障分析 | MySQL死锁案例分析

  1. sess2 在 T6 时刻执行了插入,由于插入的时候需要申请插入意向锁(insert intention lock),同样需要等待Sess1 在 T3 持有的GAP锁,两个会话循环等待对方释放锁,因此导致死锁。
感兴趣的朋友可以自己 做测试,每一步操作 都打印 show engine innodb status 查看当前事务的锁等待事件。 或者执行 SQL 查询当前事务持有或者等待的锁
select ENGINE_TRANSACTION_ID, index_name, lock_type, lock_mode, LOCK_STATUS, lock_data from performance_schema.data_locks;

2.6 如何解决

将update + insert 合并为 insert on duplicate key 的方式,避免同一个事务申请多个锁。

小结

敲黑板 ,重点: 死锁是因为不同事务对表记录加锁的顺序不一致导致相互等待对方持有的锁导致的。大家在分析死锁的时候能基于该原则去分析理清业务的sql 逻辑和执行顺序,基本上都能解决大部分的问题场景。

另外文章的最后我们再次复习一下 MySQL 的加几个基本原则,方便大家后面遇到死锁案例进行分析:

原则 1:加锁的基本单位是 next-key lock。

原则 2:查找过程中访问到的对象才会加锁。

优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。

优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

在读提交隔离级别下还有一个优化,即:语句执行过程中加上的行锁,在语句执行完成后,就要把“不满足条件的行”上的行锁直接释放了,不需要等到事务提交

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
4年前
Java中Class对象详解
<divclass"htmledit\_views"id"content\_views"<phttps://blog.csdn.net/mcryeasy/article/details/52344729<br</p<p待优化整理总结</p<p</p<h1style"padding:0px;fontfamily:'apple
Stella981 Stella981
4年前
IE iframe cookie问题(p3p)
前段时间碰到一个问题,就是在IE下,使用iFrame嵌入页面时,该页面的会话级别的cookie无法写入,导致服务端始终无法获取JSESSIONID,每次都是产生一个新的,使得Session无法使用。只需要设置P3PHTTPHeader,在隐含iframe里面跨域设置cookie就可以成功。ASP直接在头部加了头部申明,测试有效。
Wesley13 Wesley13
4年前
P2P技术详解(三):P2P中的NAT穿越(打洞)方案详解(进阶分析篇)
1、引言接本系列的上一篇《P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttp%253A%252F%252Fwww.52im
Wesley13 Wesley13
4年前
P2P技术揭秘.P2P网络技术原理与典型系统开发
Modular.Java(2009.06)\.Craig.Walls.文字版.pdf:http://www.t00y.com/file/59501950(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.t00y.com%2Ffile%2F59501950)\More.E
Stella981 Stella981
4年前
Django之Django模板
1、问:html页面从数据库中读出DateTimeField字段时,显示的时间格式和数据库中存放的格式不一致,比如数据库字段内容为2012082616:00:00,但是页面显示的却是Aug.26,2012,4p.m.答:为了页面和数据库中显示一致,需要在页面格式化时间,需要添加<td{{dayrecord.p\_time|date:
Wesley13 Wesley13
4年前
Java 9版本之后Base64Encoder和Base64Decoder无法继续使用解决办法
<divclass"htmledit\_views"id"content\_views"<p在项目开发过程中,因为重装系统,安装了Java10版本,发现sun.misc.Base64Encoder和sun.misc.Base64Decoder无法使用。</p<p<br</p<p<strong原因:</strong</p<p查看
Wesley13 Wesley13
4年前
P1
通过本文,您的收获可能有:从课下部分,了解一些基本部件搭建时可能遇到的坑点,稍微深入一点理解两种状态机的区别;从课上测试部分,可以了解重点的考察内容,明白设计时状态机的类型在测试中的重要性。课下测试部分:课下测试主要考察了splitter的实现,ALU的实现,格雷码计数器的实现,扩位器的实现,以及合法表达式判别的有限状态机问题。本次课下部分比
Stella981 Stella981
4年前
JFinal使用笔记2
大部分步骤按cf官方的教程就可以了。遇到的问题如下:1、使用C3p0Plugin配置数据库连接,代码如下//配置C3p0数据库连接池插件//C3p0Pluginc3p0PluginnewC3p0Plugin(getProperty("jdbcUrl"),getProperty("user"),getP
Wesley13 Wesley13
4年前
CDN+P2P在大规模直播&实时直播的技术实践
摘要:本次分享将介绍爱奇艺多类型的直播业务现状,以及直播整体技术架构和客户端直播网络模块Livenet的实现。回顾直播技术顺应业务多样化的演进过程,包括从偏P2P架构发展到结合CDN&P2P混合架构,为多端适配而实现的多协议支持和切换等演变,直播P2P和直播推流SDK的技术实现等。演讲/周志伟整理/LiveVideoStack
Stella981 Stella981
4年前
2018java面试集合
作者:刘成链接:https://www.zhihu.com/question/266822548/answer/317700943来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。另一个java后端面试总结:http://www.cnblogs.com/java1024/p/7685400.html39
可莉 可莉
4年前
2018java面试集合
作者:刘成链接:https://www.zhihu.com/question/266822548/answer/317700943来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。另一个java后端面试总结:http://www.cnblogs.com/java1024/p/7685400.html39
位流星云
位流星云
Lv1
风尘荏苒音书绝,关塞萧条行路难。
文章
5
粉丝
0
获赞
0