《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点,我误把生产当测试》
