首页 > Technology > 正文

MySQL 死锁排查指南:读日志、找顺序、修问题

fenij 2026-08-06 1 Technology

MySQL 死锁是两个或多个事务互相等待对方释放锁,结果谁也执行不下去。InnoDB 会自动检测死锁并回滚其中一个事务,但频繁死锁会影响性能和稳定性。排查死锁的关键是会读死锁日志,找到事务之间的循环依赖。

死锁是怎么发生的

经典场景:

  • 事务 A 先更新行 1,再请求行 2 的锁
  • 事务 B 先更新行 2,再请求行 1 的锁
  • 互相等待,形成死锁

死锁不一定是代码 bug,很多时候是业务逻辑里操作顺序不一致导致的。

开启死锁日志

InnoDB 的死锁信息默认写在错误日志里。确保日志开启:

SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';

如果是 OFF,开启它:

SET GLOBAL innodb_print_all_deadlocks = ON;

永久生效需要在 my.cnf 里加:

[mysqld]
innodb_print_all_deadlocks = 1

读取死锁日志

MySQL 错误日志通常在 /var/log/mysql/error.log/var/lib/mysql/hostname.err。搜索 LATEST DETECTED DEADLOCK

grep -A 30 "LATEST DETECTED DEADLOCK" /var/log/mysql/error.log

日志里会显示:

  • 死锁发生的时间
  • 涉及的事务 ID
  • 每个事务持有的锁和等待的锁
  • MySQL 选择回滚的事务

分析死锁日志

关键信息:

*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 12 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 123, OS thread handle 123456789, query id 456 localhost user updating
UPDATE accounts SET balance = balance - 100 WHERE id = 1

看两个事务分别持有什么锁、等待什么锁。找到循环等待的两行记录,就是死锁的根源。

解决死锁的方法

1. 统一操作顺序

所有事务按相同顺序访问记录。比如都先更新 id 小的,再更新 id 大的:

UPDATE accounts SET ... WHERE id IN (1, 2) ORDER BY id;

2. 缩短事务长度

事务里不要放不必要的查询和计算。事务越快结束,持有锁的时间越短,死锁概率越低。

3. 使用合适的隔离级别

默认 REPEATABLE READ 锁范围较大。如果业务允许,可以降到 READ COMMITTED:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

4. 加合适的索引

没有索引的 WHERE 条件会触发全表扫描,锁住大量行,增加死锁概率。

监控和预防

查看历史死锁次数:

SHOW ENGINE INNODB STATUS\G

关注 Deadlocks 计数。如果持续增长,说明代码里有系统性的顺序问题。

也可以用 performance_schema 查询最近的锁等待:

SELECT * FROM performance_schema.data_lock_waits\G

死锁排查清单

步骤 操作
1 开启 innodb_print_all_deadlocks
2 在错误日志里找到 LATEST DETECTED DEADLOCK
3 定位两个互相等待的事务
4 检查业务代码中操作记录的顺序
5 统一访问顺序或缩短事务长度
6 验证死锁频率是否下降

更多 MySQL 运维教程,请访问 fenij.com

常见问题

问:死锁和锁等待超时有什么区别?
答:死锁是循环等待,InnoDB 会自动检测并回滚一个事务。锁等待超时是一个事务等另一个事务释放锁,但等太久超过了 innodb_lock_wait_timeout,MySQL 报错而不是自动处理。

问:MySQL 回滚哪个事务?
答:InnoDB 通常选择修改行数最少(undo 最少)的事务回滚,这样回滚成本最低。但具体选择可能因版本和场景而异。

问:死锁日志里的事务 ID 能和业务请求对应起来吗?
答:直接对应比较难。建议在应用层给 SQL 加注释(如 /* order_id=12345 */),这样死锁日志里能看到注释,便于定位业务来源。