MySQL 死锁日志只看三行:谁持锁、谁在等、等哪条索引
SHOW ENGINE INNODB STATUS 输出的 LATEST DETECTED DEADLOCK 段落动辄四五十行,真正有用的只有三处:TRANSACTION 段落里的 lock_mode 与持锁索引、WAITING 那行的 waiting for this lock to be granted、以及两边 SQL 的最后一句。
先看「谁持谁等」。死锁日志里会有 TRANSACTION 1 和 TRANSACTION 2,某个标注 HOLDS THE LOCK(S),另一个标注 WAITING FOR THIS LOCK。持锁的一方是既得利益者,等锁的一方是被回滚的,InnoDB 会选择代价小的那个回滚,通常是修改行数少的。
再看锁类型。lock_mode X locks rec but not gap 是行锁,说明命中了唯一索引或主键;locks gap before rec 是间隙锁,说明用了范围条件或非唯一索引。看到 gap 锁基本可以断定:RR 隔离级别下,两个事务用了不同的索引顺序去做范围更新。经典场景是先按 A 索引更新一批,再按 B 索引更新另一批,另一个事务反过来。解法统一是让两条路径的加锁顺序一致,或者把事务拆小,别在一个事务里跨多张表乱序更新。
第三看索引。日志里 INDEX_NAME 和 lock id 会指出锁在哪个索引的哪条记录上。如果 INDEX_NAME 是 PRIMARY,说明走的是主键;如果是二级索引名,就要怀疑这条 SQL 没走预期索引,或者优化器选了另一个。加锁范围超出预期是最常见的死锁根源,用 EXPLAIN 确认实际走的索引,必要时 FORCE INDEX。
几个高频误区。一是以为加了唯一索引就不会有间隙锁,范围查询照样有。二是以为读不加锁,RR 下的快照读确实不加,但 SELECT ... FOR UPDATE 和 UPDATE 都加。三是以为重试三次能解决,死锁是设计问题,重试只是把概率往下压,压不到零。
线上处置顺序:先 SHOW ENGINE INNODB STATUS 抓现场,再 SHOW PROCESSLIST 看有没有长事务,然后 INNODB_TRX 里查 trx_started 最早的那个。长事务是死锁的温床,超过十秒的事务在高峰期就该告警。
- 点赞
- 收藏
- 关注作者
评论(0)