InnoDB主动检测并回滚代价最小事务以解除死锁,非故障而是正常策略;需通过索引优化、访问顺序统一、小事务拆分及应用层重试来降低死锁概率。
死锁不是InnoDB“没处理好”,而是它主动检测并牺牲一个事务
InnoDB 遇到死锁时不会卡住或无限等待,而是通过内置的死锁检测器(wait-for graph)在几毫秒内识别出循环等待,并选择一个代价最小的事务回滚——这个被回滚的事务会收到 Deadlock found when trying to get lock; try restarting transaction 错误。关键点在于:这不是故障,是正常策略。常见误解是“加了索引就不会死锁”,其实只要两个事务以不同顺序访问相同资源(哪怕都是走索引),就可能触发死锁。
典型死锁场景:UPDATE 顺序不一致 + 无覆盖索引
最常复现的死锁发生在多条件 UPDATE 或范围操作中,尤其当 WHERE 条件未命中唯一索引、或扫描路径不可预测时。例如:
-- 事务A UPDATE accounts SET balance = balance - 100 WHERE user_id = 1001 AND status = 'active'; -- 事务B UPDATE accounts SET balance = balance + 100 WHERE user_id = 1002 AND status = 'active';
如果 user_id 是主键但 status 没有索引,InnoDB 可能对整个二级索引 status 进行范围扫描,锁住多个间隙(gap lock),而两个事务又交叉更新不同 user_id 行,就容易形成锁等待环。
- 务必为 WHERE 中高频出现的非主键字段建立联合索引,例如
(status, user_id),让查询能走索引且扫描范围确定 - 避免在事务中执行不确定扫描范围的操作,如
SELECT ... FOR UPDATE带模糊条件(LIKE '%abc') - UPDATE 语句尽量只更新必要字段,减少锁升级概率(例如不要用
SET *)
如何快速定位死锁原因:看 SHOW ENGINE INNODB STATUS
执行该命令后,输出末尾的 LATEST DETECTED DEADLOCK 区域才是关键。它包含: