MySQL死锁怎么排查?从InnoDB状态、锁等待到SQL优化的实战指南
2026-10-10 4 0
MySQL出现死锁时,应用程序可能报出 ERROR 1213 (40001): Deadlock found when trying to get lock,部分事务被回滚,接口也可能出现失败或重试。InnoDB 默认会检测死锁,并选择一个事务回滚,让其他事务继续执行。排查的重点是找到事务之间的锁依赖关系,确认哪些 SQL 持有锁、哪些 SQL 正在等待,以及它们为什么以冲突的顺序访问数据。
使用 SHOW ENGINE INNODB STATUS 查看死锁
发现死锁后,可以先执行:
SHOW ENGINE INNODB STATUS\G
在输出中找到 LATEST DETECTED DEADLOCK,重点检查以下内容:
- TRANSACTION:发生冲突的事务及其执行状态。
- WAITING FOR THIS LOCK TO BE GRANTED:当前事务正在等待的锁。
- HOLDS THE LOCK(S):事务已经持有的锁。
- MySQL thread id:对应的数据库线程信息。
- WE ROLL BACK TRANSACTION:InnoDB 选择回滚的事务。
将两个事务的 SQL 放在一起分析,通常就能发现冲突原因。例如,事务 A 先更新订单表,再更新库存表,事务 B 却先更新库存表,再更新订单表。两边分别持有对方需要的锁时,就可能形成死锁。
这条命令主要展示最近一次死锁。线上问题如果偶尔发生,执行时可能已经出现新的死锁,因此还需要结合日志留存信息。
通过错误日志记录全部死锁
MySQL 8.0、8.4 等版本可以开启 InnoDB 死锁日志:
SET GLOBAL innodb_print_all_deadlocks = ON;
开启后,InnoDB 会将用户事务中的死锁详情写入 MySQL 错误日志。具体日志位置可以检查 log_error 配置:
SHOW VARIABLES LIKE 'log_error';
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';
生产环境建议在排查期间持续收集日志,并记录死锁发生时间、业务接口、事务涉及的表和 SQL。问题定位完成后,可根据运维策略关闭该选项,避免长期保留大量无关日志。
使用 Performance Schema 分析锁等待
如果死锁发生频繁,或者需要查看当前锁等待关系,可以查询 Performance Schema。
查看已经持有或正在申请的数据锁:
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
LOCK_TYPE,
LOCK_MODE,
LOCK_STATUS,
LOCK_DATA
FROM performance_schema.data_locks;
查看阻塞关系:
SELECT
REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx,
BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx
FROM performance_schema.data_lock_waits;
data_locks 可以帮助确认锁对应的表、索引、记录以及锁状态。data_lock_waits 则用于关联等待锁的事务和持锁事务。
需要注意,当前锁等待不一定就是死锁。普通锁等待可能在持锁事务提交后自行解除,死锁则涉及相互等待的依赖关系。另外,死锁被 InnoDB 检测并回滚后,相关锁可能已经释放,事后查询未必能还原现场,因此应结合错误日志和死锁报告分析。
排查常见的死锁原因
1. 多个事务更新顺序不同
这是常见原因。涉及多张表时,尽量统一访问顺序,例如所有业务都先更新订单,再更新库存,减少事务之间的锁冲突。
2. WHERE 条件缺少合适的索引
更新语句如果扫描大量记录,可能持有更多锁,增加并发冲突。应检查执行计划,为常用的查询和更新条件设计合适的索引。
EXPLAIN
UPDATE orders
SET status = 2
WHERE user_id = 1001 AND status = 1;
3. 事务执行时间过长
事务中包含远程接口调用、文件操作或长时间业务计算,会延长持锁时间。应尽量缩短事务范围,避免持锁期间执行与数据库无关的工作。
4. 范围查询与间隙锁
在特定隔离级别和执行条件下,范围查询、SELECT ... FOR UPDATE 等操作可能涉及间隙锁或 next-key lock。排查时要结合索引、查询范围和实际锁信息分析,不能只看 SQL 是否修改了同一条记录。
如何减少死锁并保证业务正确
优化事务时,可以统一 SQL 执行顺序,缩短事务持续时间,完善索引,并避免一次事务修改过多数据。不要仅仅为了消除死锁就随意降低事务隔离级别,因为隔离级别调整可能改变数据一致性语义,也不保证所有死锁都会消失。
应用程序还应处理死锁回滚。遇到 MySQL 错误 1213 时,可以在有限次数内重新执行完整事务,并使用短暂退避减少并发冲突。重试前要确认事务已经回滚,重试逻辑也要考虑业务操作的幂等性。对于错误 1205(锁等待超时),应单独识别和处理,它与死锁并非同一种错误。
排查 MySQL 死锁时,建议先保存 SHOW ENGINE INNODB STATUS 输出,再查看错误日志和锁等待信息,最后回到事务代码、索引和 SQL 执行顺序寻找原因。偶发死锁通常可以通过正确的重试机制处理,频繁死锁则需要结合业务并发模式进行针对性优化。