记一次项目上MySQL死锁Deadlock的排查优化过程
起因是最近两天收到了线上项目的告警通知,隔一段时间会出现几笔MySQL的死锁Deadlock的错误,错误日志如下:
updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock;
try restarting transaction ### The error may exist in com/.../XXXMapper.java (best guess)
### The error may involve com.....MsgFrameSmsMapper.update-Inline
### The error occurred while setting parameters
### SQL: UPDATE msg SET success_count=success_count+1 WHERE (batch_name = ?)
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ;
Deadlock found when trying to get lock;
try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock;
...
看了下日志位置以及执行的sql定位到了功能模块,其模块的功能概括如下图:

三方服务会给我们推送批次号以及状态的回调通知,一次通知中可能会包含多个批次号,每个批次号可能会产生多个子状态,每个子状态都需要通知到我们,我们根据子状态来决定来更新表中的数量字段。
库使用的引擎是InnoDB,batch_name加了索引,找DBA使用 SHOW ENGINE INNODB STATUS 命令查询了下mysql死锁的日志:
LATEST DETECTED DEADLOCK
------------------------
2025-05-27 10:00:29 0x7fbbb7ce7700
*** (1) TRANSACTION:
TRANSACTION 29608052, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 42 lock struct(s), heap size 8400, 47 row lock(s), undo log entries 75
MySQL thread id 11897, OS thread handle 140446306776832, query id 54085329 10.16.2.173 user_sms updating
UPDATE msg SET success_count=success_count+1 WHERE (batch_name = '1927183012550696960')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 188 page no 81 n bits 248 index ix_batch_name of table `ums_sms`.`msg` trx id 29608052 lock_mode X locks rec but not gap waiting
Record lock, heap no 98 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303132353530363936393630; asc 1927183012550696960;;
1: len 8; hex 1abebabd41802001; asc A ;;
*** (2) TRANSACTION:
TRANSACTION 29608051, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
31 lock struct(s), heap size 3520, 44 row lock(s), undo log entries 79
MySQL thread id 11906, OS thread handle 140444219373312, query id 54085339 10.16.2.173 user_sms updating
UPDATE msg SET success_count=success_count+1 WHERE (batch_name = '1927183018338836480')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 188 page no 81 n bits 248 index ix_batch_name of table `ums_sms`.`msg` trx id 29608051 lock_mode X locks rec but not gap
Record lock, heap no 78 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313832393939393830333731393638; asc 1927182999980371968;;
1: len 8; hex 1abebaba54402001; asc T@ ;;
Record lock, heap no 93 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303038333934313435373932; asc 1927183008394145792;;
1: len 8; hex 1abebabc49802001; asc I ;;
Record lock, heap no 98 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303132353530363936393630; asc 1927183012550696960;;
1: len 8; hex 1abebabd41802001; asc A ;;
Record lock, heap no 99 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303132383639343638313630; asc 1927183012869468160;;
1: len 8; hex 1abebabd54401001; asc T@ ;;
Record lock, heap no 100 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303133383637373038343136; asc 1927183013867708416;;
1: len 8; hex 1abebabd90001001; asc ;;
Record lock, heap no 141 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303139323533313934373532; asc 1927183019253194752;;
1: len 8; hex 1abebabed0c02001; asc ;;
Record lock, heap no 157 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303231303339393732333532; asc 1927183021039972352;;
1: len 8; hex 1abebabf3b802001; asc ; ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 188 page no 81 n bits 248 index ix_batch_name of table `ums_sms`.`msg` trx id 29608051 lock_mode X locks rec but not gap waiting
Record lock, heap no 129 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 19; hex 31393237313833303138333338383336343830; asc 1927183018338836480;;
1: len 8; hex 1abebabe9a802001; asc ;;
意思就是:
事务1 (29608052) 尝试执行sql语句:
UPDATE msg SET success_count=success_count+1 WHERE (batch_name = '1927183012550696960');
等待锁定的记录是batch_name = '1927183012550696960'
事务2(29608051) 执行了几个批次的update语句,其中包括:
UPDATE msg SET success_count=success_count+1 WHERE (batch_name = '1927183012550696960');
也就是事务2已经获得了batch_name = '1927183012550696960'的排它锁,然后等待锁定的记录是batch_name = '1927183018338836480',也就是尝试执行下面的sql语句:
UPDATE msg SET success_count=success_count+1 WHERE (batch_name = '1927183018338836480');
根据mysql的死锁日志可以推断出,事务1在等待batch_name='1927183012550696960'也就是被事务2执行的排它锁(堆号98),事务2在等待batch_name='1927183018338836480'的排它锁(堆号129),而这条记录被事务事务1(或其他事务,但根据日志上下文可推断为事务1)间接依赖或锁定,以此形成了循环依赖。
项目中会通过p6spy打印sql的执行日志,根据traceId看了下批次号1927183012550696960和1927183018338836480都存在于两条MQ消息中,说明这两个批次号都接收到了两次通知,这个三方服务本来只有一家,当时对接的时候约定同一个批次号下的子状态会聚合,不会出现一个批次号通知多次的场景,但是后面随着后面其它厂商的接入,对接的时候没有确定这一点,导致了一个批次号多次推送从而出现了上述的问题,并且这个问题也不是必现,正好要碰到这样的场景才会触发。
好在一条MQ消息中的某一个批次号执行失败,整个MQ消息的事务会回滚,然后其中一条MQ消息进入重试队列,重试之后就不会出现事务争抢的情况,因为MySQL会选择死锁中的一个事务执行成功,根据MySQL日志观察是事务2被回滚了(事务2回滚的日志没有贴出),事务1被提交。
毕竟有这些错误日志告警持续产生,得想办法解决了,首先想到的就是化数据库的锁为redis的锁了,也就是在执行某一个批次号的更新动作时,先使用redis获取这一个批次号的锁,获得锁之后进行操作,否则等待,不过这还牵扯出一个新的问题,就是一条消息里面的多个批次在处理时是一个完整的事务,只有等待所有批次处理完成后,才能释放掉这条消息中每一个批次的锁,初步方案如下:

方案不够好,还有优化的空间,想了下,可以借助MQ的唯一消息id和重试队列来实现,缩小事务范围,将一条消息中的批次号作为一个事务,将循环批次的代码放在事务外面,一个批次处理成功、事务提交成功之后通过redis记录,key是MQ的msgId+批次号,value按需设置,我这边存储的是批次号处理成功时候的时间戳,因为后面重试这条消息里面处理失败的批次号时,跳过处理这条消息里面成功的批次号是根据msgId+批次号去redis里查询的。
项目上使用的MQ是RocketMQ,RocketMQ从4.7.1版本开始,一条消息重试时消息的msgId会变化,但是可以通过读取消息中的属性UNIQ_KEY来获取原始的msgId,RocketMQ的MessageClientIDSetter类中也提供该方法:
public static String getUniqID(final Message msg) {
return msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
}
那么改版后的方案如下:

这个方案的好处就是一个事务里面不会牵扯到多个批次号,化大事物为小事物,也就避免了交叉获取批次号的锁从而发生死锁的问题,也不用使用redis对每一个批次号进行加锁,跨事务操作更新某一个批次号的数据时,其中一个事务会等待另一个事务提交之后执行。
记一次项目上MySQL死锁Deadlock的排查优化过程的更多相关文章
- 记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理
昨晚我正在床上睡得着着的,突然来了一条短信. 啥,线上MySQL死锁了,我赶紧登录线上系统,查看业务日志. 能清楚看到是这条insert语句发生了死锁. MySQL如果检测到两个事务发生了死锁,会回滚 ...
- 记一次线上MySQL数据库死锁问题
最近线上项目报了一个MySQL死锁(DealLock)错误,虽说对业务上是没有什么影响的,由于自己对数据库锁这块了解不是很多,之前也没怎么的在线上碰到过.这次刚好遇到了,便在此记录一下 ...
- 记一次Win上MySQL乱码问题
Win上MySQL乱码问题 笔记本上的数据库总会时不时的乱码(或者是一直乱码我没注意到?),在谷歌上试了几次错才正确解决,在此记录一下. 在MySQL数据库存储目录找到my.ini,在相应的标签下分别 ...
- Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程
前言 遇到Mysql死锁问题,我们应该怎么排查分析呢?之前线上出现一个insert on duplicate死锁问题,本文将基于这个死锁问题,分享排查分析过程,希望对大家有帮助. 死锁案发还原 表结构 ...
- 原创 记录一次线上Mysql慢查询问题排查过程
背景 前段时间收到运维反馈,线上Mysql数据库凌晨时候出现慢查询的报警,并把原始sql发了过来: --去除了业务含义的sql update test_user set a=1 where id=1; ...
- 一次线上mysql死锁分析
一.现象 发运车次调用发车接口时发生异常,后台抛出数据库死锁日志. 二.原因分析 通过日志可以看出事务T1等待 heap no 8的行锁 (X locks 排他锁) 事务T2持有heap no 8的行 ...
- 记一次线上Mysql数据库 宕机
从发现问题,到最后解决一共消耗两个半小时(7:30~10:00),报警电话16通,警察坐镇,未完待续 ......
- 记一次FTP上传文件总是超时的解决过程
好久没写博,还是重拾记录一下吧. 背景:买了一个阿里云的云虚拟机用来搭建网站(起初不了解云虚拟主机和云服务器的区别,以为都是有SSH功能的,后来发现不是这样样子啊,云虚拟机就是FTP上传网页+MySQ ...
- 一则线上MySql连接异常的排查过程
Mysql作为一个常用数据库,在互联网系统应用很多.有些故障是其自身的bug,有些则不是,这里以前段时间遇到的问题举例. 问题 当时遇到的症状是这样的,我们的应用在线上测试环境,JMeter测试过程中 ...
- J2EE Oa项目上传服务器出现的乱码解决过程
(= =)搞了许久觉得有必要记下来.. 由于我本地的mysql都设置好了,但是服务器的又不能去改它 毕竟还有其他人要用- -: 所以只能是我建的时候去设置一下了, 首先先建数据库 ,表;; creat ...
随机推荐
- (dify)如何使用dify自定义知识库【dify外部链接知识库】
尝试dify自定义知识库 根据官网教程,可以从知识库的右上角外部知识库进行添加外部知识库 前往 "知识库" 页,点击右上角的 "外部知识库 API",轻点 &q ...
- ModelForm验证笔记
Form验证 UserInfoForm --> Form -->BaseForm(is_valid...) UserInfoModelForm -->ModelForm ...
- 使用离线部署32B模型实现OpenDeepWiki项目代码自动分析与文档生成
背景介绍 在企业环境中,我们经常需要对公司项目代码进行分析和文档生成.然而,考虑到代码的保密性,将代码上传至公共AI平台存在安全隐患.为解决这一问题,我们可以在公司内部GPU服务器上部署强大的大语言模 ...
- React Native开发鸿蒙Next---react-native-cameraroll在ArkTS下的接入报错
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 浅析区块链BaaS平台定位
一.概述 区块链是一种聚合了分布式存储.密码学.链式结构.p2p通讯,使得链上数据具有防篡改.可追溯等特点的一种信任技术. 继数字藏品热潮之后,2023年以来,区块链的发展进入了Gartner成熟度曲 ...
- Mysql高级操作(select嵌套,多表JOIN)
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 【中文】【吴恩达课后编程作业】Course 4 - 卷积神经网络 - 第三周作业
[中文][吴恩达课后编程作业]Course 4 - 卷积神经网络 - 第三周作业 - 车辆识别 上一篇:[课程4 - 第三周测验]※※※※※ [回到目录]※※※※※下一篇:[课程4 - 第四周测验] ...
- springboot中mybatis报错
反正有关于mybatis报错的,问题肯定就是mybatis这几个文件之中. 要么就是Mapper类少注解,要么就是mybatis配置文件中的namespace java.lang.IllegalArg ...
- uni-app项目loading显示方案
前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE可视化的运行和打包也让开发体验也非常棒,公司项目就是主推uni-app, ...
- springboot~入门第三篇~与mybatis整合~(未完)
第二部分仅仅是从控制器到页面的跳转,但是没数据库的整合是不行的. 进入正题: springboot启动是要默认加载数据源的,之前是从application.properties,现在开始在 src/ ...