news 2026/9/3 0:26:47

<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/9/2 22:22:37

《把脉行业与技术趋势》-113-万物都是一个有序、自动、受控的完成特定功能和性能的,有无数个环构成的系统,都需能量完成维持和功能转换,企业,通信,网路,产品,生物体,皆如此。

万物都是一个有序、自动、受控的完成特定功能和性能的,由无数个环构成的系统,都需能量完成维持和功能转换,企业,通信,网路,产品,生物体,皆如此,或开环或闭环,…

作者头像 李华
网站建设 2026/8/23 17:07:19

英伟达千亿美元 OpenAI 投资生变:AI 巨头博弈下的算力与资本新棋局

引言 当 OpenAI 以 ChatGPT 掀起全球 AI 狂潮时,它与英伟达的绑定关系曾被视为 “AI 时代的 Wintel 联盟”—— 前者定义大模型的能力边界,后者用 GPU 筑牢算力底座。而近期黄仁勋的一句 “千亿美元投资并非承诺”,却让这对黄金搭档的资本纽带蒙上了一层迷雾。这不仅是两家…

作者头像 李华
网站建设 2026/9/2 23:55:03

学长亲荐!AI论文工具 千笔 VS 文途AI,更适合本科生的写作选择!

随着人工智能技术的迅猛发展,AI辅助写作工具已逐渐成为高校学生完成毕业论文的重要帮手。从开题报告到文献综述,从大纲构建到正文撰写,越来越多的学生开始借助这些工具提升写作效率、降低学术压力。然而,面对市场上种类繁多的AI写…

作者头像 李华
网站建设 2026/8/19 16:45:04

<span class=“js_title_inner“>MySQL参数max_binlog_cache_size设置不当引发的从库复制中断的案例</span>

日常运维中的坑真是防不胜防,不一小心就遇到别人给你挖的坑。想起多年前生产环境中遇到的因经验不足的DBA不知道从哪拷贝的配置文件(据说是当时参加某培训机构视频培训时资料里的模板,真的是误人子弟呀)放在生产环境而导致数据同步…

作者头像 李华
网站建设 2026/9/2 22:13:08

Flutter艺术探索-Flutter自定义Widget:从零开始创建组件

Flutter自定义Widget:告别“搭积木”,从零构建你的专属组件 引言:当内置组件不够用时 搞Flutter开发,Container、Text、Row这些内置组件就像是工具箱里的标准件,应付日常的UI搭建绰绰有余。但做项目不是搭积木&#xf…

作者头像 李华