死锁高发主因是索引设计不当而非SQL错误;联合索引字段顺序必须将高频等值条件置左,范围查询置右,否则易致索引半失效、间隙锁扩散或全表扫描,大幅升高死锁概率。

死锁高发往往不是SQL写错了,而是索引没建对——字段顺序错、范围查询放太前、等值条件没前置,都会让行锁变间隙锁、让单行锁变全表扫描。
为什么联合索引字段顺序直接决定死锁概率
MySQL 行锁加在索引记录上,执行计划是否走索引、锁多少行,全由 WHERE 条件和索引字段顺序的匹配度决定。顺序不对,索引就可能“半失效”:
- 比如业务里高频执行
WHERE pool_id = ? AND status = ?,但索引建成了(pool_id, identifier, status, is_redeemed),而identifier允许为 NULL,InnoDB 就无法跳过它直接用status,最终只走pool_id前缀 → 触发间隙锁扩散 → 锁住大量无关行 - 正确顺序应是把高频等值条件放最左:
(pool_id, status, is_redeemed, identifier),确保 WHERE 严格按序命中前缀 - 范围查询(如
created_at > '2024-01-01')必须放在联合索引末尾,否则后续字段无法被索引驱动
UPDATE/DELETE 没走索引是死锁最大推手
没有索引的更新语句会触发全表扫描,InnoDB 可能对每行都加 X 锁(甚至升级为表锁),不同事务扫描顺序不同,极易形成交叉加锁——这不是“偶尔撞上”,而是每次执行都在扩大死锁面。
- 用
EXPLAIN UPDATE ...检查key列:若为NULL或非预期索引,说明没走对索引 - 避免在索引列上做函数操作,例如
WHERE ABS(score) > 80会让score索引完全失效,应改写为score > 80 OR score - JOIN 场景下,
ON和WHERE共同决定索引选择;例如JOIN order_items ON a.id = b.order_id WHERE b.created_at > ?,order_items上必须有(order_id, created_at)联合索引,单建created_at索引毫无意义
如何验证索引调整是否真降低了死锁
别只看 EXPLAIN 或慢查询日志——死锁是否缓解,得看真实并发下的锁行为是否收敛。
- 开启持久化死锁日志:
innodb_print_all_deadlocks = ON,并确认log_error路径可写,否则你永远只能看到最后一次死锁 - 复现时抓
SHOW ENGINE INNODB STATUS\G中的LATEST DETECTED DEADLOCK,重点比对两件事:TABLE和INDEX是否相同、lock_mode是否从X locks gap before rec变成更精确的X locks rec but not gap - 如果调整后死锁仍集中在同一张表、同一索引、同一锁模式,说明索引没切中真正瓶颈,可能是事务顺序或隔离级别问题
最容易被忽略的一点:索引优化只是降低死锁的必要条件,不是充分条件。即使索引完美,若多个事务以不同顺序更新同一组主键(比如一个按 id ASC 更新,另一个按 id DESC),照样死锁。所以索引 + 统一锁序 + 缩短事务,三者缺一不可。











