死锁发生后应立即执行show engine innodb status\g查看最近一次死锁详情,而非重启;该命令仅保留最后一次死锁信息,需快速抓取latest detected deadlock区块中的事务id、锁类型、等待/持有锁等关键信息。

死锁发生后第一反应不是重启,而是立刻抓取状态
MySQL死锁本身不报错给应用(除非事务被自动回滚并抛出 Deadlock found when trying to get lock),但真正卡住的是后续等待的事务。这时候靠查 SHOW PROCESSLIST 看不到死锁本身,只能看到一堆 Waiting for table metadata lock 或 Updating 的长时等待——那是结果,不是原因。
必须立刻执行:
SHOW ENGINE InnoDB STATUS\G。注意加
\G,否则输出挤在一行根本没法读。这个命令返回的是最近一次死锁的完整现场,包括两个(或多个)事务各自的 SQL、持有的锁、等待的锁、事务 ID、隔离级别、行记录主键值等。它不保存历史,只保留最后一次,所以发现疑似死锁就得马上跑。从 LATEST DETECTED DEADLOCK 区块里定位关键信息
输出里最核心的是 LATEST DETECTED DEADLOCK 这一段。别从头扫日志,直接跳到这里。重点关注三块:
- 每个事务的
TRANSACTION行:记下id和name(比如mysql-thread-id: 12345) - 每个事务的
mysql tables in use和locked tables:确认是否锁了同一张表,甚至同一索引 - 最关键的是
WAITING FOR THIS LOCK TO BE GRANTED和HOLDS THE LOCK(S)两行:一个在等什么锁(比如record lock, heap no 123),另一个持有什么锁(比如gap before rec lock)。这里暴露了锁类型冲突本质——常见是 A 持有 gap lock 等 record lock,B 反过来
示例中若看到:
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 123 page no 456 n bits 72 index `idx_user_id` of table `db`.`orders` trx id 123456789 lock_mode X locks gap before rec insert intention waiting——说明事务 1 在插入前试图获取插入意向锁,但被事务 2 的 gap lock 挡住了。
为什么用 SHOW ENGINE 看不到你刚遇到的死锁?
这不是命令失效,是 MySQL 的设计机制:InnoDB 只保留最近一次死锁的完整上下文。如果中间又发生了另一次死锁,前一次就被覆盖。生产环境高频写入下,很可能你还没连上服务器,死锁已经刷新了三次。
解决方案只有两个:
- 在业务层捕获
Deadlock found when trying to get lock错误时,立刻触发一个后台任务去执行SHOW ENGINE InnoDB STATUS并落库/发告警 - 开启死锁日志持久化:
SET GLOBAL innodb_print_all_deadlocks = ON(MySQL 5.6.2+),它会把每次死锁都追加到错误日志里,路径由innodb_log_error控制,默认是stderr或 MySQL 错误日志文件
注意:innodb_print_all_deadlocks 是全局动态变量,但修改后对已存在的连接不生效,需确保新连接能读到新值;另外它不记录事务 SQL 全文,只记录锁信息,仍需结合慢日志或 general log 补全上下文。
查到 SQL 后别急着改索引,先看事务边界和执行顺序
很多人看到死锁里出现 UPDATE orders SET status = ? WHERE user_id = ? AND status = 'pending',第一反应是“给 user_id 加索引”。但往往没用——因为 user_id 如果本就有索引,InnoDB 已经用了;真正问题是这条 UPDATE 在事务里跑得太晚,前面已经持有了其他行的锁。
更常见的根因是:
- 事务跨了多个无关表操作(比如先更新用户余额,再插入订单,最后更新商品库存),人为扩大了锁持有范围
- 应用用了
SELECT ... FOR UPDATE但没走索引,导致锁整张表或大量间隙 - 批量操作用
IN列表,但列表顺序在不同事务里不一致(A 按 id 升序更新,B 按创建时间降序),导致加锁顺序不一致
验证方法很简单:把死锁里涉及的 SQL 单独拿出来,在测试库开两个连接,按日志里相同的顺序和参数手动重放,大概率能复现。复现后就能试改事务粒度、加锁顺序、或拆成更小事务。
复杂点在于,有些死锁依赖极短的时间窗口和特定数据分布,本地很难稳定复现;这时候得靠 innodb_lock_wait_timeout 缩短等待时间,让问题更快暴露,而不是无限挂起。











