news 2026/6/9 17:56:54

<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/5/26 18:25:17

揭秘appium滑动屏幕技巧—实现用户仿真动作的多重方式

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快在移动端应用中,基于简便的原因,用户通常会倾向于使用滑动操作来达到与应用程序中的控件进行交互的,这使得滑动成为自动化测试中…

作者头像 李华
网站建设 2026/6/8 8:52:39

深入理解大模型微调Checkpoint:从文件结构到生产部署

文章目录一 Checkpoint文件结构解析1.1 核心架构文件1.2 HuggingFace Trainer 原生状态1.3 分词器(Tokenizer)文件1.4 DeepSpeed ZeRO 优化器状态(分布式训练特有)1.5 Checkpoint 不能直接推理二 利用保存点进行推理的方法2.1 方案…

作者头像 李华
网站建设 2026/6/5 3:41:48

基于multisim的红外发射与报警接收电路设计

(1)设计一个红外发射器调制频率为30kHz。 (2)设计一个红外接收器,当无人遮挡红外光时,报警器不发报警信号。当有人遮挡光时,报警器发报警信号频率为800Hz。 (3)控制距离2m以上。 仿真图: 仿真演示与文件下载:基于mult…

作者头像 李华
网站建设 2026/6/5 17:55:10

Linux C/C++组件编译全解析:从源码到可执行文件的奥秘

引言:为什么需要了解文件后缀? 在Linux C/C开发中,不同文件后缀代表着不同的编译阶段和用途。作为开发者,理解这些后缀的含义不仅有助于构建系统,还能在调试和优化时提供重要线索。本文将基于QEMU项目中virtio-balloon…

作者头像 李华
网站建设 2026/6/9 1:45:55

CPU/内存/硬盘/网络信息提取——工业级一句话指令集

文章目录 🚀 CPU/内存/硬盘/网络信息提取——工业级一句话指令集 🔍 核心设计原则 🖥️CPU 信息(物理/逻辑/频率) 1. 物理CPU数 + 逻辑CPU数 + 每核线程数 2. 物理CPU型号 + 主频(实时 + 标称) 3. CPU架构 + 字长 + 字节序 4. CPU缓存层级(L1/L2/L3) 5. NUMA节点拓…

作者头像 李华
网站建设 2026/6/1 7:26:35

2026年,Agent与APP必有一战

旧钥匙打不开新大门,旧地图找不到新大陆。 刚过去的2025年,AI炙手可热,人工智能第一次走进人类日常生活——前所未有地通过手机AI甚至AI手机。 但颠覆与创新,也总是伴随“争议”。 从近年手机厂运用AI算法辅助,让更多人…

作者头像 李华