关键词死锁InnoDB锁等待间隙锁死锁日志死锁预防大家好我是小耶写功课只是为了我踩过的坑你们别再踩了半夜两点手机响了。钉钉群里一片哀嚎“订单系统挂了大量Deadlock found”你打开SHOW ENGINE INNODB STATUS看到LATEST DETECTED DEADLOCK下面一大段日志——十六进制地址、锁结构体、各种缩写每个字母都认识串起来完全看不懂。死锁不是bug它是数据库并发控制机制的必然产物。区别在于有人能在几分钟内定位根因并解决有人说“重启试试”然后继续睡。今天我们从死锁的四种常见模式出发建立一套从日志到根因的完整分析链。一、死锁的四种常见模式在深入日志之前先建立分类框架。不同类型的死锁日志特征和解决方案完全不同。模式1不同表顺序死锁场景事务A先更新orders再更新users事务B先更新users再更新orders。日志特征两个事务各持有一张表的锁等待另一张表。根因代码中未统一跨表操作的加锁顺序。模式2相同表不同条件死锁场景事务A通过二级索引锁定行1事务B通过主键锁定行2但索引交错形成循环。日志特征两个事务都涉及同一张表但通过不同索引路径形成循环等待。根因复合索引设计问题导致不同查询走了不同的索引路径。模式3间隙锁死锁RR隔离级别下最常见场景事务A范围查询锁住了间隙事务B也想在同一个间隙插入数据。日志特征日志中出现locks gap before rec和insert intention。根因RR隔离级别下间隙锁与插入意向锁冲突。模式4外键约束死锁场景高并发下更新父表时需要检查子表子表上有行锁。日志特征锁等待链涉及父表和子表。根因外键约束在并发场景下放大锁冲突。二、死锁日志逐行解码以下是一个典型的死锁日志片段------------------------ LATEST DETECTED DEADLOCK ------------------------ *** (1) TRANSACTION: TRANSACTION 310298, ACTIVE 0 sec UPDATE orders SET status PAID WHERE order_id 10086 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 100 page no 3 n bits 72 index PRIMARY of table db.orders trx id 310298 lock_mode X locks rec but not gap *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 100 page no 5 n bits 72 index idx_status of table db.orders trx id 310298 lock_mode X locks gap before rec insert intention waiting *** (2) TRANSACTION: TRANSACTION 310299, ACTIVE 0 sec UPDATE orders SET status SHIPPED WHERE status PAID *** (2) HOLDS THE LOCK(S): RECORD LOCKS index idx_status ... lock_mode X *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS index PRIMARY ... waiting *** WE ROLL BACK TRANSACTION (1)关键字段解读字段含义分析价值TRANSACTION事务ID区分两个死锁事务HOLDS THE LOCK当前已持有的锁知道对方占用了什么资源WAITING FOR THIS LOCK正在等待的锁知道自己在等什么lock_mode X排他锁写锁冲突locks rec but not gap行锁非间隙锁普通行锁冲突locks gap before rec间隙锁RR隔离级别特有常与插入意向锁冲突WE ROLL BACK被回滚的事务谁被牺牲了从日志还原死锁过程事务1持有主键order_id10086的行锁在等idx_status上的锁。事务2持有idx_status上的锁在等主键锁。两个事务形成循环等待 → 死锁发生事务1被回滚。三、从日志特征反推死锁模式日志特征死锁模式根因两个事务各持不同表的锁不同表顺序代码未统一加锁顺序同一张表不同索引路径相同表不同条件复合索引设计问题gap before recinsert intention间隙锁RR隔离级别涉及父表和子表外键约束高并发下外键开销大四、真实案例间隙锁导致的死锁场景库存扣减系统先查询是否存在可用库存再更新。并发高时频繁死锁。日志特征*** (1) HOLDS: lock_mode X locks gap before rec *** (1) WAITING: insert intention *** (2) HOLDS: lock_mode X locks gap before rec *** (2) WAITING: insert intention分析RR隔离级别下事务A执行SELECT ... FOR UPDATE范围查询锁住了间隙事务B同样锁住相同间隙两个事务都想插入新数据互相等待插入意向锁形成死锁。解决方案将隔离级别改为READ COMMITTEDRC模式下不存在间隙锁同时配合binlog_formatROW保证复制安全。五、死锁预防清单绝大多数频繁死锁的问题根源就两个锁顺序混乱、事务太长。1. 统一加锁顺序跨表操作时所有事务严格按相同顺序访问表和行。例如先更新orders再更新users所有事务都按这个顺序。2. 拆分长事务事务越短越好避免在事务中调用外部API或做耗时操作。长事务意味着持有锁的时间更长死锁概率呈指数级上升。3. 优化索引设计死锁往往是索引交错导致的。分析死锁日志中涉及的两个索引考虑是否可以通过调整索引来打破循环。4. 降低隔离级别如果业务允许幻读将REPEATABLE READ降为READ COMMITTED间隙锁消失从根本上避免间隙锁相关死锁。5. 应用层重试机制捕获Deadlock found异常后重试通常1-2次即可成功这是最直接的兜底方案。6. 开启死锁日志SET GLOBAL innodb_print_all_deadlocks ON把所有死锁记录进错误日志方便长期追踪。死锁是一个可以系统化分析的问题。通过“分类→日志解码→反推根因→预防”四步法你不仅能解决当前死锁还能建立预防机制。绝大多数死锁都源于两个核心问题锁顺序混乱、事务太长。把这两个问题解决再配合索引优化和隔离级别调整死锁就会从“半夜惊醒”变成“日常可控”。小耶在手SQL 不愁还有什么想了解的欢迎留言小耶一定知无不言言无不尽……我们下次见~