死锁不是bug而是高并发下事务资源竞争的自然结果,InnoDB会自动检测并回滚代价小的事务;根本解决需统一加锁顺序、优化索引避免锁扩大、缩短事务时长、加强可观测性。

死锁不是 bug,而是高并发下事务资源竞争的自然结果。InnoDB 会自动检测并回滚代价小的事务,但频繁死锁意味着设计存在隐患——核心问题从来不是“谁被回滚”,而是“为什么形成循环等待”。
统一加锁顺序:打破循环等待最直接的手段
死锁的循环等待本质,源于事务对资源访问顺序不一致。只要所有事务按同一逻辑顺序获取锁,闭环就无法形成。
- 单表多行更新时,强制按主键或唯一索引升序处理(如
WHERE id IN (100, 50, 200)改为先查SELECT id FROM t WHERE id IN (100, 50, 200) ORDER BY id,再按序更新) - 跨表操作时,约定全局顺序:比如按业务语义定为「用户 → 订单 → 库存 → 支付」,所有服务代码必须严格遵循
- 批量任务中避免“先查后更”的分散加锁,改用
SELECT ... FOR UPDATE一次性锁定全部目标行(注意:必须显式指定 WHERE 条件且走索引)
索引与锁粒度:避免无谓的锁扩大
没有索引的 WHERE 条件会让 InnoDB 退化为全表扫描加锁,相当于把行锁升级成表锁,冲突面急剧放大。
- 检查所有 UPDATE/DELETE 的 WHERE 字段是否都有对应索引,特别是业务常用查询条件(如 order_no、mobile、out_trade_no)
- 避免在非索引字段上执行
FOR UPDATE;若必须按非主键更新,优先加唯一索引,而非容忍全表锁 - 使用
EXPLAIN验证执行计划,确认实际走了预期索引;警惕隐式类型转换导致索引失效(如字符串字段传整数)
事务瘦身与隔离控制:减少锁持有窗口
锁不是被“争”出来的,而是被“拖”出来的。长事务像一张缓慢收拢的网,不断捕获新请求。
- 把耗时操作(如远程调用、文件读写、复杂计算)移出事务体,只保留纯粹的数据库变更
- 调整事务边界:用
@Transactional(propagation = Propagation.REQUIRES_NEW)拆分复合操作,避免一个大事务卡住多个资源 - 评估是否可降级隔离级别:RR(Repeatable Read)默认启用间隙锁,易引发范围类死锁;若业务允许,考虑 RC(Read Committed)+ 唯一约束兜底
可观测与主动防御:让死锁从故障变成信号
线上死锁不应靠报警才发现,而应作为系统健康度的常规指标来监控。
- 开启
innodb_print_all_deadlocks = 1,将每次死锁写入错误日志,配合 ELK 或 Loki 做聚合分析 - 定期执行
SHOW ENGINE INNODB STATUS\G,重点关注 LATEST DETECTED DEADLOCK 区段,还原 SQL 执行路径和锁等待链 - 应用层实现指数退避重试(如首次 10ms,二次 20ms,三次 40ms),对错误码 1213 自动捕获并重放,降低用户感知


















