死锁不是MySQL bug,而是加锁顺序冲突的必然结果;根本原因是多个事务以不同顺序申请同一组行锁导致循环等待,InnoDB自动回滚一个事务,但逻辑缺陷不修复就会反复发生。

死锁不是MySQL bug,而是加锁顺序冲突的必然结果
MySQL遇到Deadlock found when trying to get lock; try restarting transaction报错,根本原因从来不是“数据库太忙”或“配置太低”,而是两个及以上事务以不同顺序申请了同一组行锁,形成循环等待。InnoDB会自动选一个事务回滚,但问题不解决,下次还会触发——它不是偶然故障,是并发逻辑缺陷的稳定暴露。
排查死锁必须看SHOW ENGINE INNODB STATUS\G里的LATEST DETECTED DEADLOCK
这个命令输出里只有LATEST DETECTED DEADLOCK段落有用,其他全是干扰信息。它会明确告诉你:
- 参与死锁的两个事务ID(
TRANSACTION后一串十六进制) - 每个事务正在执行的SQL(
mysql tables in use和query字段) - 各自持有(
HOLDS THE LOCK(S))和等待(WAITING FOR THIS LOCK TO BE GRANTED)的锁类型、索引名、记录位置(如space id 587 page no 3 n bits 72) - 锁的粒度:是
RECORD LOCKS(行锁)、gap before rec(间隙锁),还是insert intention(插入意向锁)
注意:SHOW ENGINE INNODB STATUS只保留最近一次死锁,线上持续发生时必须配合innodb_print_all_deadlocks = ON写入错误日志,否则你永远看不到历史现场。
死锁日志里最常被忽略的三个线索
很多人扫一眼日志就去改代码,结果复现不了。真正关键的信号藏在细节里:
-
*** (1) TRANSACTION:和*** (2) TRANSACTION:的SQL顺序要逐行比对——哪个事务先锁了哪条记录、后请求哪条,直接暴露加锁顺序不一致 - 如果看到
gap before rec,说明当前隔离级别(默认REPEATABLE READ)启用了间隙锁,哪怕WHERE条件命中唯一索引,范围查询或INSERT也可能被堵住 - 若
WAITING FOR THIS LOCK TO BE GRANTED指向一个不存在的主键值(比如id = 999999),大概率是事务在等插入意向锁,而另一事务正持有一个覆盖该间隙的gap lock
为什么开了innodb_print_all_deadlocks还找不到日志?
常见卡点不在配置本身,而在路径和权限:
- 确认
log_error变量指向的路径真实存在且MySQL进程有写权限:SELECT @@log_error; - Linux下检查SELinux是否拦截:
ausearch -m avc -ts recent | grep mysqld - 日志文件可能被logrotate切走,用
find /var/log/mysql -name "error.log*" -ls查全量 - MySQL 8.0+中,
performance_schema.data_lock_waits表可实时查锁等待链,但需提前开启相关instrumentation
死锁日志本身不包含应用层堆栈,它只反映数据库内核视角的资源争抢。想定位到具体哪段Java/Go代码触发了这两个冲突SQL,必须把事务ID(trx_id)和应用日志中的trace_id做时间戳关联——这是多数人止步的地方。











