返回

MySQL死锁怎么排查?从InnoDB状态、锁等待到SQL优化的实战指南

2026-10-10 MySQL 死锁 SQL 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 执行顺序寻找原因。偶发死锁通常可以通过正确的重试机制处理,频繁死锁则需要结合业务并发模式进行针对性优化。

顶部