MySQL 死锁排查工作流:从 InnoDB 状态到事务顺序优化
MySQL 死锁不是“数据库坏了”,而是两个或多个事务互相等待对方持有的锁,最后 InnoDB 主动回滚其中一个事务。线上看到的现象通常是接口偶发失败、日志里出现 deadlock、用户刷新后又好了。
这类问题最怕只盯着报错重试,而不去看事务访问顺序。本文按完整工作流梳理:从应用报错到 InnoDB 状态,从锁等待环到事务顺序优化,最后加上有限重试和上线检查点。
- 目标和边界
- 全流程总览
- 阶段一:抓到死锁现场
- 阶段二:还原锁等待环
- 阶段三:统一事务顺序和缩短锁持有时间
- 阶段四:业务侧加有限重试保护
- 我的推荐流程
- 容易踩坑
- 落地速查表
目标和边界
本文讨论的是 InnoDB 行锁场景下的典型死锁排查,不展开 MySQL 内核实现,也不把主题扩展成全量性能优化。读完后,你应该能完成三件事:
- 知道从哪里拿到最近一次死锁信息。
- 能根据事务、SQL、锁等待关系还原死锁链路。
- 能用统一加锁顺序、短事务和有限重试降低线上影响。
先说结论:死锁不是完全可以消灭的异常,尤其在高并发更新场景里,业务侧必须有重试保护。但重试不能替代治理,真正要修的是事务访问顺序、锁范围和锁持有时间。
全流程总览
一个实用的 MySQL 死锁排查流程可以拆成五步:应用报错、查看 InnoDB 状态、识别锁等待环、调整事务顺序、加重试保护。每一步都有明确产物,不能只停留在“日志里有 deadlock”。

| 阶段 | 目标 | 关键动作 | 检查点 |
|---|---|---|---|
| 抓现场 | 确认是不是 InnoDB 死锁 | 记录错误码、接口、请求参数和事务入口 | 能关联到具体业务操作 |
| 看状态 | 读取最近一次死锁详情 | 使用 SHOW ENGINE INNODB STATUS | 能看到两个事务和等待的锁 |
| 还原链路 | 找出循环等待关系 | 对比 SQL、索引、where 条件、加锁顺序 | 能画出谁等谁 |
| 修事务 | 减少再次死锁概率 | 统一访问顺序,减少事务内慢操作 | 高并发压测死锁次数下降 |
| 做保护 | 降低偶发死锁对用户的影响 | 有限重试、幂等校验、失败告警 | 重试次数可观测,失败可回溯 |
阶段一:抓到死锁现场
目标
先确认问题是不是死锁,而不是普通锁等待超时、连接池耗尽或接口超时。死锁通常会伴随 “Deadlock found when trying to get lock” 这类信息,被选中的事务会回滚。
关键动作
应用层日志至少记录四类信息:业务动作、请求 ID、SQL 所在方法、事务开始和结束位置。没有这些上下文,即使拿到了数据库死锁信息,也很难映射回代码。
request_id=pay-20260617-001 action=pay_order db_error=Deadlock found when trying to get lock tx_entry=PayService.payOrder order_id=10086 sku_id=SKU-9
数据库侧先查看最近一次死锁详情:
SHOW ENGINE INNODB STATUS\G
如果线上死锁比较频繁,排查窗口内可以临时打开全部死锁记录,让信息进入 MySQL 错误日志。排查完成后要按团队规范评估是否关闭,避免日志过多影响观察。
SET GLOBAL innodb_print_all_deadlocks = ON;
常用工具/代码选择
小团队可以先用应用日志加 `SHOW ENGINE INNODB STATUS`;复杂系统建议把请求 ID、事务入口、SQL 模板和数据库错误码打到统一日志里,方便按时间线检索。
检查点
你应该能回答:哪一个接口触发了死锁、哪两个 SQL 参与了冲突、哪个事务被回滚、发生时间是否和业务峰值重合。
阶段二:还原锁等待环
目标
死锁排查的核心不是记住某条 SQL,而是还原循环等待:事务 A 拿着什么锁、还想要什么锁;事务 B 拿着什么锁、又在等什么锁。
关键动作
从 InnoDB 状态中摘出两个事务的 SQL、等待的索引、记录锁类型和回滚结果。然后回到业务代码,看这些 SQL 是不是在同一个事务里按不同顺序访问了相同资源。
事务 A: 1. 更新订单表 order_id=10086 2. 继续更新库存表 sku_id=SKU-9 事务 B: 1. 更新库存表 sku_id=SKU-9 2. 继续更新订单表 order_id=10086 结果: A 等库存表,B 等订单表,形成循环等待。
还要检查索引是否命中。如果 where 条件没有走到合适索引,锁范围会扩大,原本只想改一行,结果锁住更多记录或间隙,死锁概率会被放大。
常用工具/代码选择
可以结合 `EXPLAIN` 看访问路径,用业务日志确认事务顺序,用压测脚本复现高并发场景。不要只在本地单线程点几次接口,那通常复现不出真实锁冲突。
检查点
排查到这一步,应该能画出一张等待关系图:事务 A 持有订单锁并等待库存锁,事务 B 持有库存锁并等待订单锁。画不出来,就说明证据还不够。
阶段三:统一事务顺序和缩短锁持有时间
修死锁时,最常见也最有效的思路是统一资源访问顺序。只要所有路径都按同一种顺序加锁,就能减少“你等我、我等你”的循环等待。

目标
让所有涉及相同资源的事务按统一顺序访问,并尽量缩短锁被持有的时间。
关键动作
假设支付成功后要改订单和扣库存,团队可以约定所有事务都先锁订单,再锁库存。另一条补偿、取消、库存回写链路也要遵守同一顺序。
-- 推荐:所有入口都按相同顺序访问资源 START TRANSACTION; SELECT id FROM orders WHERE id = 10086 FOR UPDATE; SELECT sku_id FROM stock WHERE sku_id = 'SKU-9' FOR UPDATE; UPDATE orders SET status = 'paid' WHERE id = 10086; UPDATE stock SET quantity = quantity - 1 WHERE sku_id = 'SKU-9' AND quantity > 0; COMMIT;
同时把事务里的非数据库动作移出去,例如远程接口调用、文件处理、复杂计算、消息发送。事务内只保留必须一起提交的数据库读写,锁持有时间越短,冲突窗口越小。
常用工具/代码选择
可以在服务层封装事务模板,把资源访问顺序写成团队约定;对核心表建立访问规范,例如订单相关事务先订单后明细,库存相关事务先库存主表后流水表。
检查点
检查所有入口:正向下单、取消订单、退款回滚、定时补偿、后台人工修复,是否都遵守同一加锁顺序。只改一个接口,另一个入口仍然反向访问,死锁还会回来。
阶段四:业务侧加有限重试保护
目标
即使事务顺序已经优化,高并发下仍可能出现偶发死锁。业务侧要把死锁当成可重试异常处理,但重试必须有限、幂等、可观测。
关键动作
只对明确的死锁错误做有限重试,例如最多 2 到 3 次,每次短暂退避。重试前确认业务动作具备幂等保护,例如订单支付不能重复扣款,库存扣减不能重复生成流水。
def run_with_deadlock_retry(action, max_retry=3):
for i in range(max_retry):
try:
return action()
except DeadlockError:
if i == max_retry - 1:
raise
sleep_ms(50 * (i + 1))
如果重试成功,要记录一次可观测事件;如果最终失败,要把请求 ID、订单号、事务入口和重试次数打到告警里,方便后续复盘。
常用工具/代码选择
Java、Go、Python 都可以在数据访问层或应用服务层封装重试。关键不是语言,而是不要把所有数据库错误都重试,也不要无限重试。
检查点
上线后观察两条曲线:死锁次数是否下降,重试后成功率是否稳定。如果死锁次数不降反升,说明事务顺序或锁范围还没有治理到位。
我的推荐流程
真正处理线上死锁时,我建议按下面顺序来:
- 先记录应用错误码、请求 ID、业务参数和事务入口。
- 立刻查看 `SHOW ENGINE INNODB STATUS`,保存最近一次死锁文本。
- 把两个事务的 SQL 摘出来,标记它们持有什么锁、等待什么锁。
- 回到业务代码,查所有会访问这些表的入口,不只看报错接口。
- 统一资源访问顺序,补齐必要索引,移出事务内慢动作。
- 加有限重试和幂等保护,并把重试次数纳入监控。
- 用并发压测复现旧路径,再验证新路径死锁次数是否下降。
容易踩坑
| 坑点 | 表现 | 修法 |
|---|---|---|
| 只加重试 | 接口表面恢复,数据库死锁仍然很多 | 继续排查事务顺序和锁范围 |
| 只看一条 SQL | 误以为某个 UPDATE 本身有问题 | 把两个事务的完整顺序都还原出来 |
| 索引缺失 | where 条件锁到更多记录 | 补齐高选择性索引,并用访问计划确认 |
| 事务里做慢动作 | 锁持有时间变长,高峰期更容易冲突 | 把远程调用、消息发送、复杂计算移到事务外 |
| 补偿链路被忽略 | 主流程修好了,定时任务仍然反向加锁 | 统一梳理所有入口的资源访问顺序 |
落地速查表
| 检查项 | 最低要求 | 上线前确认 |
|---|---|---|
| 应用日志 | 记录请求 ID、业务参数、事务入口和数据库错误 | 能从日志定位到代码路径 |
| 死锁文本 | 保存 InnoDB 最近一次死锁详情 | 能识别两个事务和等待关系 |
| 索引检查 | 核心 where 条件命中合适索引 | 不会意外扩大锁范围 |
| 事务顺序 | 所有入口按同一资源顺序加锁 | 补偿、后台、定时任务也已检查 |
| 事务时长 | 事务内不放远程调用和慢逻辑 | 锁持有时间可被压测观察 |
| 重试保护 | 仅对死锁做有限重试,并保证幂等 | 重试成功率和最终失败都有监控 |
总结一下,MySQL 死锁排查要从“报错”走向“等待关系”,再从“等待关系”回到“事务设计”。把现场、锁环、顺序、索引、短事务和有限重试串起来,死锁就不再是只能碰运气的线上问题。
AI Agent 工具调用失败排查:从 Schema 到超时兜底的完整工作流
- 上一篇
- AI Agent 工具调用失败排查:从 Schema 到超时兜底的完整工作流
- 下一篇
- Go map 并发读写为什么会崩:从 fatal error 到三种安全改法
-
- 数据库 · MySQL | 2天前 | MySQL · 慢查询 · 索引优化 · COUNT查询 · 汇总表 · 联合索引 覆盖索引 汇总表 MySQL COUNT慢 COUNT(*)优化
- MySQL COUNT(*) 总数查询变慢怎么办:从扫描行数到汇总表的完整治理流程
- 329浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 424次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 437次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 402次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 578次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 558次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览
-
- MySQL数据表简单查询
- 2023-01-10 101浏览

