MySQL 死锁怎么排查?
更新时间:2026-09-18。问题驱动短答页。死锁不是数据库「坏了」,而是两个事务互相持有对方想要的锁形成环,InnoDB 检测到后主动回滚代价较小的那个。
一句话回答
立刻执行 SHOW ENGINE INNODB STATUS,看 LATEST DETECTED DEADLOCK 段:它记录了两个事务分别持有什么锁、在等什么锁、对应的 SQL。顺着锁等待环找到根因(几乎都是加锁顺序相反或间隙锁),再统一加锁顺序、缩短事务。
第一步:取死锁日志
sql
SHOW ENGINE INNODB STATUS\G重点看这一段:
bash
------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION: -- 事务1
*** (1) HOLDS THE LOCK(S): -- 它持有的锁(哪条记录/哪个索引)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: -- 它在等的锁
*** (2) TRANSACTION: -- 事务2
*** (2) HOLDS THE LOCK(S):
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
*** WE ROLL BACK TRANSACTION (2) -- InnoDB 回滚了谁读日志技巧:看锁加在哪个索引上(index "PRIMARY" / 二级索引名)、是 X locks rec but not gap(记录锁)还是 lock_mode X locks gap(间隙锁),以及每条 UPDATE/DELETE ... WHERE 的条件。
第二步:在线定位事务与锁
sql
SELECT * FROM information_schema.innodb_trx; -- 当前事务、开始时间、执行的 SQL
SELECT * FROM performance_schema.data_locks; -- MySQL8:具体锁
SELECT * FROM performance_schema.data_lock_waits; -- MySQL8:锁等待关系
-- MySQL 5.7:information_schema.innodb_locks / innodb_lock_waits长事务迟迟不提交也会放大锁冲突,先把运行时间最长的事务找出来。
最常见的两类根因
- 多行加锁顺序相反:事务 A 先锁 id=1 再等 id=2,事务 B 先锁 id=2 再等 id=1。批量
UPDATE ... WHERE id IN (...)时 InnoDB 实际按索引顺序加锁,两条语句 IN 列表顺序不同就可能成环。 - 间隙锁/Next-Key Lock(RR 隔离级别):
WHERE走范围或唯一记录不存在时,会锁住记录之间的间隙,两个事务向同一间隙插入就互锁。
治理手段
| 手段 | 说明 |
|---|---|
| 统一加锁顺序 | 批量更新先把 id 排序再更新,让所有事务按同一顺序拿锁(最有效) |
| 缩短事务 | 事务里不要夹 RPC、用户输入;先做查询计算,最后开事务快速提交 |
| 走唯一/精确索引 | 条件命中唯一索引等值且记录存在时只加记录锁,避免范围间隙锁 |
| 降低隔离级别到 RC | Read Committed 关闭大部分间隙锁,配合业务可接受不可重复读(很多互联网公司默认 RC) |
| 降低死锁影响 | 应用层捕获 Deadlock found(错误码 1213)自动重试,重试要保证幂等 |
坑
- 只看被回滚的那条 SQL:死锁是两个事务的环,必须同时看 (1)(2) 持有的锁,单看报错落点会误判。
- 以为只有
UPDATE才加锁:SELECT ... FOR UPDATE、外键检查、唯一键冲突也会加锁。 - 开了死锁日志但没开监控:
innodb_print_all_deadlocks可把所有死锁写进 error log,避免LATEST只保留最后一条被覆盖。
从死锁日志逐行分析到加锁顺序的完整实战见 数据库死锁实战:从死锁日志到加锁顺序;锁与事务机制见 MySQL 锁与事务。
总结
排查 MySQL 死锁先用 SHOW ENGINE INNODB STATUS 读 LATEST DETECTED DEADLOCK 段,理清两个事务各自持有和等待的锁及对应索引(区分记录锁与 gap 锁),再用 innodb_trx/data_locks/data_lock_waits 在线定位;根因绝大多数是多行加锁顺序相反复或 RR 下的间隙锁,治理靠批量 id 排序后统一加锁、缩短事务不夹 RPC、条件走唯一索引、必要时降到 RC 隔离级别,并让应用捕获 1213 错误做幂等重试。