show engine innodb status\g 可快速查看最后一次死锁,重点分析 latest detected deadlock 区块;开启 innodb_print_all_deadlocks 可持久记录全部死锁,但需谨慎以防日志暴增;定位根因应聚焦 holds、waiting 和 roll back 三类信息,并结合业务代码排查交叉更新或间隙锁冲突。

直接看最近一次死锁:用 SHOW ENGINE INNODB STATUS\G
MySQL 只保留**最后一次检测到的死锁**完整信息,这是最快速、最常用的入口。执行命令后,重点盯住 LATEST DETECTED DEADLOCK 区块——它不是“所有死锁”,而是“上一个”。
常见错误现象:
• 执行完命令却看不到死锁段落 → 说明近期没触发死锁,或已被新死锁覆盖
• 日志里只有“*** (1) TRANSACTION”但缺“(2)” → 可能是日志被截断,或事务已提交/回滚导致上下文丢失
实操建议:
• 一定要加 \G,否则锁信息挤在一行根本没法读
• 在业务高峰期出问题时,立刻连上数据库执行,别等第二天
• 注意时间戳:日志顶部显示的是该状态生成时间,不是死锁发生时间(二者可能差几秒)
想长期捕获所有死锁?谨慎开启 innodb_print_all_deadlocks
默认情况下,MySQL 只把死锁记进错误日志(error.log)的末尾,且仅限最近一条;开启该参数后,**每次死锁都会追加记录**,不覆盖、不丢弃。
配置方法(需重启或动态设置):SET GLOBAL innodb_print_all_deadlocks = ON;
或写入 my.cnf:[mysqld]innodb_print_all_deadlocks = ON
为什么说要谨慎?
• 错误日志会暴增:高频死锁场景下,一天可能生成几十MB纯死锁文本
• 不影响性能,但干扰日志分析:真正的报错、启动异常会被淹没在大量死锁片段里
• MySQL 5.6+ 支持,但 5.7 之前不支持动态设置,必须重启
从死锁日志里快速定位冲突点:盯住三行关键字段
一份典型死锁日志里真正有用的就三类信息,其他全是干扰项:*** (1) HOLDS THE LOCK(S):事务1现在攥着哪些锁(比如索引 idx_user_id 上的 X 锁)*** (1) WAITING FOR THIS LOCK:事务1卡在这儿,等着事务2释放某个锁*** WE ROLL BACK TRANSACTION (1):MySQL 选谁当牺牲品(被回滚的那个)
容易踩的坑:
• 看到 WAITING FOR THIS LOCK 就以为是“对方没释放”,其实可能是自己先锁了 A 再去抢 B,而对方先锁了 B 再抢 A —— 顺序反了才是根因
• space id 和 page no 是物理页定位,对排查没直接帮助,除非你要做底层页分析
• 最后执行的 SQL(日志末尾的 UPDATE ...)才是业务逻辑入口,别光看锁,忘了查这条语句在代码里怎么写的
死锁不是数据库故障,是业务逻辑暴露出来的竞争缺陷
很多团队花几小时调参、加索引、改隔离级别,结果下个版本照样死锁——因为没动到根源。
典型模式就两种:
• 交叉更新:事务A按 [user, order] 顺序更新,事务B按 [order, user] 更新 → 必然成环
• 间隙锁碰撞:WHERE 条件是范围(如 WHERE age BETWEEN 20 AND 30),两个事务各自锁住不同间隙,又试图插入重叠位置
实操建议:
• 查日志时同步翻代码:看报错堆栈里那条 UPDATE 是在哪个 service 方法里发的,有没有循环批量操作
• 用 SELECT ... FOR UPDATE 前,确认是否真需要强一致性;很多时候加缓存、改异步、引入分布式锁更治本
• 如果是消息队列触发的并发更新(比如你看到日志里有 ConsumerThread1/2),优先考虑消费端加 userId 级别串行化,而不是在 DB 层硬扛










