直接看死锁日志就能定位问题根源,关键抓事务顺序、锁类型和资源路径;通过SHOW ENGINE INNODB STATUS\G查看LATEST DETECTED DEADLOCK,分析时间戳、两个事务的持有锁与等待锁、SQL原文,再比对操作顺序、检查索引有效性、确认隔离级别影响,结合代码层统一访问顺序、拆分事务、预校验加锁及智能重试,并开启innodb_print_all_deadlocks配合日志监控与埋点实现主动防控。

直接看死锁日志就能定位问题根源,关键不是“有没有日志”,而是“会不会读”——重点抓事务顺序、锁类型和资源路径三个信息点。
怎么看懂 LATEST DETECTED DEADLOCK 日志
执行 SHOW ENGINE INNODB STATUS\G,在输出中找到 LATEST DETECTED DEADLOCK 模块。它不是乱码,而是一份结构化“事故报告”:
-
时间戳:死锁发生的具体时间(如
2026-07-28 15:22:34),可对齐应用日志排查业务上下文 -
两个事务块:分别标为
TRANSACTION 12345和TRANSACTION 67890,每块含:-
HOLDS THE LOCK(S):当前事务已持有的锁(例如:X lock on indexPRIMARYof tabledb.order,行 ID 是id=1001) -
WAITING FOR THIS LOCK:正在等待的锁(例如:X lock on indexidx_user_idof tabledb.account,whereuser_id=2002) -
mysql tables in use和lock struct(s):涉及表名、索引名、锁结构数量,判断是否误触全表锁 -
ROLLING BACK行:MySQL 自动选中的牺牲者(通常改写为“WE ROLL BACK TRANSACTION 12345”)
-
-
SQL 语句原文:每个事务最后会列出触发锁的 SQL(如
UPDATE order SET status='paid' WHERE id=1001),这是还原业务逻辑的关键线索
三步锁定根本原因
别只盯着“谁被回滚了”,要顺藤摸瓜找冲突链:
-
比对操作顺序:看事务 A 先锁了
order再等account,事务 B 是否反过来先锁account再等order?——这是最常见也最易修复的“顺序不一致” -
检查索引有效性:若
WAITING FOR显示锁的是GEN_CLUST_INDEX或没指定具体行,大概率是 UPDATE/DELETE 语句未命中索引,导致升级为表锁或间隙锁扩大范围 -
确认隔离级别影响:在
REPEATABLE READ下,SELECT ... FOR UPDATE或范围条件(如WHERE amount > 100)会隐式加间隙锁,可能把本不该锁的记录也圈进来
Java 侧快速落地的解决办法
数据库配置调优只是辅助,真正见效快的是代码层控制:
立即学习“Java免费学习笔记(深入)”;
-
强制统一资源访问顺序:比如所有转账类操作,约定必须先更新
account表(按 user_id 升序),再更新order表(按 order_id 升序)。可用工具类封装排序逻辑,避免人工出错 - 拆分长事务:把“查用户余额 → 扣减 → 查订单 → 更新状态 → 发通知”这种大事务,拆成“扣减 + 更新”小事务(核心数据变更),其余非关键步骤异步处理
-
加锁前预校验:在事务开始前,用
SELECT ... FOR UPDATE SKIP LOCKED尝试获取锁,失败则快速返回重试或降级,避免卡在等待队列里被动卷入死锁 -
捕获并智能重试:在 Spring 中用
@Retryable(value = {DeadlockLoserDataAccessException.class}, maxAttempts = 3),配合指数退避,比简单 try-catch 更稳妥
让死锁不再“悄悄发生”
光靠事后分析不够,得建立主动防线:
- 在 MySQL 配置中开启
innodb_print_all_deadlocks = 1,确保每次死锁都落盘到error.log,别只依赖SHOW ENGINE看最后一次 - 用脚本定时扫描错误日志,匹配
Deadlock found关键字,自动提取事务 ID 和 SQL,推送到告警群或工单系统 - 在 Java 应用的 DAO 层统一拦截
DeadlockLoserDataAccessException,打点埋点(如 Prometheus 指标mysql_deadlock_total{method="transfer"}),监控趋势比单次分析更有价值


















