《100个“反常识”经验12:死锁日志怎么看?》

本期摘要

数据库突然卡死,应用日志里一堆“Deadlock found”。你遇到过吗?死锁不是Bug,是并发事务资源竞争的正常现象。本文不讲复杂的死锁理论,直接带你读MySQL死锁日志:怎么看事务1在等什么锁、事务2拿着什么锁、谁被回滚了。看完你就能在三分钟内定位到问题SQL,而不是盲目重启。

一次让人想砸键盘的故障

业务高峰期,突然大量接口超时。应用日志里全是:

text

Deadlock found when trying to get lock; try restarting transaction

登录MySQL,执行:

sql

SHOW ENGINE INNODB STATUS\G

找到 LATEST DETECTED DEADLOCK 这一段。看到日志的瞬间两眼一黑——全是十六进制地址和晦涩的锁信息,根本不知道在说什么。

死锁日志到底在说什么?

一条典型的死锁日志长这样:

sql

*** (1) TRANSACTION: TRANSACTION 310479085, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 104 page no 45 n bits 80 index idx_user_id of table `db`.`orders` trx id 310479085 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 310479086, ACTIVE 10 sec starting index read mysql tables in use 1, locked 1 4 lock struct(s), heap size 1136, 3 row lock(s) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 104 page no 45 n bits 80 index idx_user_id of table `db`.`orders` trx id 310479086 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 104 page no 52 n bits 80 index idx_status of table `db`.`orders` trx id 310479086 lock_mode X locks rec but not gap waiting *** (2) ROLLBACK TO SAVE POINT

拆开来看,每个字段都有含义:

字段含义对我们有用的信息
TRANSACTION 310479085事务ID区分两个事务
ACTIVE 12 sec事务活跃时间超过几秒就要警惕
waiting for this lock在等哪个锁告诉你被卡在哪个索引上
holds the lock(s)持有哪些锁告诉你对方占了什么
index idx_user_id索引名称定位到具体表和索引
tabledb.orders表名哪个表出问题了
ROLLBACK谁被回滚了事务2被干掉,事务1继续

两分钟定位问题的思路

第一步:找到事务1等什么

看 (1) WAITING FOR THIS LOCK:它被卡在idx_user_id上,在等一个行锁。

第二步:找到事务2占什么

看 (2) HOLDS THE LOCK(S):它占着idx_user_id上的锁,同时在等idx_status上的锁。

第三步:得出结论

事务1需要 user_id=xxx 的锁,事务2占着它不放,同时事务2又在等另一条记录的锁。循环等待。

第四步:回看业务代码

两个事务的SQL分别是:

事务1:

sql

UPDATE orders SET status = 'paid' WHERE user_id = 123 AND order_id = 456;

事务2:

sql

UPDATE orders SET status = 'shipped' WHERE status = 'paid' AND order_id = 456;

事务1先锁住了 user_id 索引,事务2锁住了 order_id 索引,互相等待。

如何解决?

临时方案:kill掉被回滚的事务对应的应用连接,释放锁。

永久方案

  • 统一事务内的SQL顺序,让所有事务按相同顺序访问表和索引

  • 优化索引,减少行锁范围

  • 拆分大事务,缩短锁持有时间

  • 使用 READ COMMITTED 隔离级别,减少间隙锁

快速排查脚本

sql

-- 查看当前锁等待 SELECT * FROM information_schema.innodb_lock_waits; -- 查看当前事务 SELECT * FROM information_schema.innodb_trx WHERE trx_state = 'LOCK WAIT';

下期预告

《100个“反常识”经验13:凌晨3点,我误把生产当测试》