news 2026/4/18 10:18:26

<span class=“js_title_inner“>别对着报错发呆了!手把手教你还原 MySQL 死锁的“案发现场”</span>

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
<span class=“js_title_inner“>别对着报错发呆了!手把手教你还原 MySQL 死锁的“案发现场”</span>
关注我们,设为星标,每天7:30不见不散,每日java干货分享

你的电商系统正在进行大促。突然,支付服务疯狂报错:
java.sql.SQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
你的反应:
你知道发生了死锁,但你不知道是哪两个业务逻辑撞车了。
你颤颤巍巍地在数据库里敲下了那行命令:

SHOW ENGINE INNODB STATUS\G;

屏幕上吐出了一大坨像乱码一样的日志。别慌,我们只看LATEST DETECTED DEADLOCK这一节。


1. 核心原理:死锁日志的“三段式”结构

死锁日志记录的是案发那一刻的快照。它通常由三部分组成:

  1. 1.事务 (1) (TRANSACTION 1):“受害者”或者“凶手”之一。它手里拿着什么锁,正在等什么锁。

  2. 2.事务 (2) (TRANSACTION 2):另一个“凶手”。它手里拿着什么锁,正在等什么锁。

  3. 3.判决结果 (WE ROLL BACK TRANSACTION):MySQL 的裁判(死锁检测器)决定杀掉哪个事务来打破僵局。


2. 实战解码:经典“AB-BA”死锁

这是最容易读懂的死锁类型。

日志片段还原:
------------------------ LATEST DETECTED DEADLOCK ------------------------ *** (1) TRANSACTION: TRANSACTION 12345, ACTIVE 5 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 100, OS thread handle ..., query id ... # 注意:这里显示的是事务 1 正在尝试执行的 SQL UPDATE accounts SET balance = balance - 100 WHERE id = 1 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: # 事务 1 正在等 ID=1 的 X 锁(排他锁) RECORD LOCKS space id 54 page no 4 n bits 72 index PRIMARY ... trx id 12345 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000001; asc ;; -- 这里 hex 1 代表 ID=1 *** (2) TRANSACTION: TRANSACTION 12346, ACTIVE 3 sec starting index read ... # 事务 2 正在尝试执行的 SQL UPDATE accounts SET balance = balance + 100 WHERE id = 2 *** (2) HOLDS THE LOCK(S): # 事务 2 手里已经拿到了 ID=1 的 X 锁! RECORD LOCKS space id 54 page no 4 n bits 72 index PRIMARY ... trx id 12346 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000001; asc ;; -- 又是 ID=1 *** (2) WAITING FOR THIS LOCK TO BE GRANTED: # 事务 2 正在等 ID=2 的 X 锁 ... hex 80000002 ... *** WE ROLL BACK TRANSACTION (1)
侦探分析:
  1. 1.HOLDS THE LOCK(S):事务 2 持有 ID=1 的锁。

  2. 2.WAITING FOR THIS LOCK:事务 1 想要 ID=1 的锁(被阻塞)。事务 2 想要 ID=2 的锁。

  3. 3.推导逻辑:

  • 事务 1:已经锁住了 ID=2 (虽然日志没显式写它持有,但因为它在等 ID=1,且形成了死锁,说明它手里必有筹码),现在想锁 ID=1。

  • 事务 2:已经锁住了 ID=1,现在想锁 ID=2。

  1. 4.结论:典型的资源顺序冲突。

  • • 线程 A:Lock(2) -> Lock(1)

  • • 线程 B:Lock(1) -> Lock(2)

实战场景:两个用户互相转账。


3. 进阶解码:看不懂的“间隙锁” (Gap Lock)

很多时候,你发现日志里只有INSERT语句,并没有 Update,为什么也会死锁?
这时候要关注关键词:lock_mode X locks gap before recinsert intention

日志片段还原:
*** (1) TRANSACTION: INSERT INTO users (id, name) VALUES (10, 'Alice') *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS ... index PRIMARY ... # 注意关键词:insert intention (插入意向锁) lock_mode X locks gap before rec insert intention waiting *** (2) TRANSACTION: INSERT INTO users (id, name) VALUES (10, 'Bob') *** (2) HOLDS THE LOCK(S): # 注意关键词:locks gap before rec (间隙锁) RECORD LOCKS ... index PRIMARY ... lock_mode X locks gap before rec
侦探分析:
  1. 1.场景:这是一个INSERT导致的死锁,通常发生在唯一索引冲突或范围删除后。

  2. 2.解读:

  • 事务 2持有一个Gap Lock(间隙锁)。这通常是因为它之前执行了一个DELETE FROM users WHERE id > 5或者SELECT ... FOR UPDATE,锁住了一片范围。

  • 事务 1想要在这个范围内INSERTid=10。插入操作需要获取Insert Intention Lock(插入意向锁)。

  • 规则:插入意向锁会被间隙锁排斥。

  1. 3.隐形杀手:如果事务 2 自己也想在这个间隙里插入数据,或者两个事务同时对同一个不存在的记录加锁(SELECT * FROM t WHERE id = 10 FOR UPDATE),就会形成死锁。

实战场景:

  • 并发初始化数据:两个线程同时检测到数据不存在,同时执行插入(INSERT IGNOREINSERT ... ON DUPLICATE KEY)。

  • 消息队列消费:多个消费者同时处理幂等逻辑。


4. 关键术语对照表 (Rosetta Stone)

读日志时,只要看懂这几个词,能解决 90% 的问题:

术语

含义

人话解释

lock_mode X

排他锁 (Exclusive)

“我要改这条数据,谁也别动”

lock_mode S

共享锁 (Shared)

“我要读这条数据,你们别改,但可以读”

locks rec but not gap

记录锁 (Record Lock)

“我只锁这一行,不锁前后的缝隙”

locks gap before rec

间隙锁 (Gap Lock)

“我锁的是这行前面的空隙,禁止插入”

insert intention

插入意向锁

“我想插队,那个拿着间隙锁的大哥让让路?”

Next-Key Lock

临键锁

记录锁 + 间隙锁(默认级别下的锁)


5. 总结与行动指南

当你拿到死锁日志后,按照以下步骤行动:

  1. 1.找 SQL:在日志里找到TRANSACTION 1TRANSACTION 2分别在执行什么 SQL。

  2. 2.找索引:index PRIMARY还是index idx_name,确定是锁主键还是锁二级索引(二级索引死锁非常常见)。

  3. 3.看模式:

  • • 如果是AB-BA(互斥锁):调整代码里的加锁顺序,保证所有线程都按ID升序加锁。

  • • 如果是Gap / Insert Intention(间隙锁):优化索引,尽量让 Update/Delete 命中唯一索引(退化为行锁),减少锁的范围。

最后一句忠告:
SHOW ENGINE INNODB STATUS只保留最后一次死锁的信息。如果死锁频发,建议开启全局参数innodb_print_all_deadlocks = ON,让每一次死锁都记录到 MySQL 的错误日志(error.log)里,方便事后复盘。

推荐阅读 点击标题可跳转

50个Java代码示例:全面掌握Lambda表达式与Stream API

16 个 Java 代码“痛点”大改造:“一般写法” VS “高级写法”终极对决,看完代码质量飙升!

为什么高级 Java 开发工程师喜爱用策略模式

精选Java代码片段:覆盖10个常见编程场景的更优写法

提升Java代码可靠性:5个异常处理最佳实践

为什么大佬的代码中几乎看不到 if-else,因为他们都用这个...

还在 Service 里疯狂注入其他 Service?你早就该用 Spring 的事件机制了

看完本文有收获?请转发分享给更多人

关注「java干货」加星标,提升java技能

❤️给个「推荐 」,是最大的支持❤️

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 0:30:55

2026版最全面Java面试汇总(面试题+答案)

今年的行情,让招聘面试变得雪上加霜。已经有不少大厂,如腾讯、字节跳动的招聘名额明显减少,面试门槛却一再拔高,如果不用心准备,很可能就被面试官怼得哑口无言,甚至失去了难得的机会。 现如今,…

作者头像 李华
网站建设 2026/4/17 4:13:27

惊!IF持续下跌,中科院1区Top或将降为2区

🔥 🔥 🔥 🔥 《IEEE Transactions on Neural Networks and Learning Systems》由IEEE计算智能学会于 1990 年创刊,在神经网络、机器学习、深度学习及相关交叉领域享有极高的学术声誉和影响力。 值得注意的是&a…

作者头像 李华
网站建设 2026/4/18 1:57:52

基于Redis的缓存穿透与雪崩解决方案

运行效果:https://lunwen.yeel.cn/view.php?id=5462 基于Redis的缓存穿透与雪崩解决方案 摘要:随着互联网技术的飞速发展,Redis作为一种高性能的内存数据结构存储系统,被广泛应用于缓存领域。本文针对Redis缓存系统可能出现的缓存穿透和雪崩问题,提出了一系列解决方案。…

作者头像 李华
网站建设 2026/4/18 2:07:37

什么是回归测试,冒烟测试,渗透测试

一、先讲个故事:一栋楼的“三次验收” 假设你是开发商,盖了一栋居民楼,现在要交付给业主住。整个过程里,有三种完全不同的“检查”,分别对应:冒烟测试、回归测试、渗透测试。1. 冒烟测试:交房前…

作者头像 李华
网站建设 2026/4/18 2:07:31

从零开始学虚拟化:安全加固全指南(ESXi+vCenter + 虚拟机)虚拟化环境的安全风险具有 “牵一发而动全身” 的特性 —— 宿主主机漏洞可能导致全集群虚拟机被渗透,vCenter 权限滥用可

从零开始学虚拟化:安全加固全指南(ESXivCenter 虚拟机) 虚拟化环境的安全风险具有 “牵一发而动全身” 的特性 —— 宿主主机漏洞可能导致全集群虚拟机被渗透,vCenter 权限滥用可能引发数据泄露,跨虚拟机攻击可能突破…

作者头像 李华