最快速可靠的死锁诊断方式是执行SHOW ENGINE INNODB STATUS\G,重点查看LATEST DETECTED DEADLOCK区块中的事务ID、HOLDS/WAITING锁信息及末尾SQL语句;长期监控需配置innodb_print_all_deadlocks=ON并检查返回值为1。

直接看最近一次死锁详情:SHOW ENGINE INNODB STATUS\G
这是最快速、最可靠的入口,不需要提前开日志,MySQL 自动保留最后一次死锁的完整现场。执行后重点看 LATEST DETECTED DEADLOCK 区块——它不是历史汇总,而是真实发生时的“快照”。
- 事务 ID(
TRANSACTION xxx)和活跃时间能帮你关联业务日志里的请求链路 -
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK是核心:一个在“占着”,一个在“等着”,二者形成闭环就是死锁根源 - 末尾的 SQL 语句(如
UPDATE users SET ... WHERE id = ?)必须抄下来,它是你回溯业务逻辑的唯一锚点 - 注意:这个命令只返回最后一次死锁;如果想持续捕获,必须配合配置项开启死锁日志(见下一条)
长期监控必须开死锁日志:innodb_print_all_deadlocks = ON
仅靠 SHOW ENGINE INNODB STATUS 容易漏掉高频但被覆盖的死锁。要在 my.cnf 的 [mysqld] 段落里加这一行,并重启 MySQL:
innodb_print_all_deadlocks = ON
日志会输出到错误日志文件(通常是 /var/log/mysql/error.log 或 datadir/hostname.err),每发生一次就追加一段,格式和 INNODB STATUS 里看到的一致,但可查历史。
- 别开
general_log替代——它太重,性能损耗大,且不聚焦锁行为 - 线上环境慎用
innodb_status_output_locks = ON,它会让INNODB STATUS输出巨量锁细节,影响诊断效率 - 确认是否生效:执行
SELECT @@innodb_print_all_deadlocks;,返回1才算成功
从日志里定位冲突本质:盯住索引名 + 锁类型 + 页号
死锁日志里真正有用的不是 SQL 文本,而是资源定位信息。例如这一段:
*** (1) WAITING FOR THIS LOCK: RECORD LOCKS space id 88 page no 7 index `idx_account`<br>*** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 88 page no 7 index `idx_account`
说明两个事务卡在同一张表的同一个索引、同一页上——这大概率是“相同条件并发更新”导致的;但如果出现:
*** (1) WAITING FOR THIS LOCK: RECORD LOCKS space id 88 page no 3 index `PRIMARY`<br>*** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 88 page no 7 index `idx_account`
那基本就是“访问顺序不一致”:事务 A 先查 idx_account 再改主键,事务 B 反过来先改主键再查 idx_account,环路就形成了。
-
space id和page no可以结合INFORMATION_SCHEMA.INNODB_SYS_TABLES查到具体表名 - 出现
GAP LOCK或NEXT KEY LOCK提示?说明是 RR 隔离级别下的间隙锁冲突,常见于WHERE x BETWEEN ? AND ?或未命中索引的范围查询 - 如果日志里反复出现
GEN_CLUST_INDEX(聚簇索引),说明 SQL 没走二级索引,正在锁整行甚至引发全表扫描级争抢
别只盯着数据库,检查业务代码里的隐式顺序
很多死锁根本不在 SQL 层面暴露,而是由 ORM 或业务逻辑埋下的伏笔。比如 MyBatis 动态 SQL 中 <if test="xxx"> 导致 where 条件顺序随机;或 GORM 更新时字段顺序不固定,让 MySQL 解析出不同执行计划,进而申请锁的顺序不同。
- 用
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX实时查正在运行的事务,看trx_query是否真如你预期那样执行 - 对比两个死锁事务的 SQL,不仅要看内容,还要比索引使用顺序(
EXPLAIN结果)、是否用了FOR UPDATE、有没有ORDER BY影响扫描路径 - 批量操作(如
UPDATE ... WHERE id IN (1,2,3,...))和单条更新混用时,InnoDB 内部可能按不同顺序加锁,这是高频死锁场景,务必统一为先ORDER BY id再更新
真正难的不是看懂日志,而是把 space id 88 page no 7 和你代码里第 234 行的 updateUserBalance() 调用链对应起来。日志只是镜子,照出的是业务设计里没意识到的并发假设。










