DELETE触发死锁本质是多事务竞争同一资源时加锁顺序不一致导致循环等待,InnoDB复用SELECT...FOR UPDATE机制,通过next-key lock加锁,索引使用不当、全表扫描、混合操作等均会加剧风险。

DELETE 语句为什么会触发死锁?
MySQL 的 DELETE 死锁,本质是多个事务在竞争同一组资源时加锁顺序不一致导致的循环等待。InnoDB 不会为 DELETE 单独设计一套锁逻辑——它复用 SELECT ... FOR UPDATE 的加锁机制:先定位记录(走索引),再对匹配行加 next-key lock(间隙锁 + 记录锁)。只要两个事务扫描/锁定的索引范围有重叠,且加锁顺序相反,就可能死锁。
典型诱因包括:
- WHERE 条件未命中索引,导致全表扫描 → 锁住大量无关行
- 复合索引使用不当(如跳过最左前缀),使优化器退化为范围扫描而非点查
- 事务中混合执行
UPDATE和DELETE,且涉及不同索引路径 - 高并发下相同 WHERE 条件的
DELETE被分散到不同事务,但扫描起始点或优化器选择的索引不同
如何用 SHOW ENGINE INNODB STATUS 定位死锁根因
死锁发生后,MySQL 会自动回滚代价较小的事务,并把完整上下文写入 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 区域。关键要看三块内容:
-
事务持有的锁:关注
lock_mode X locks rec but not gap或lock_mode X locks gap before rec,确认锁类型和记录位置 -
事务正在等待的锁:看
waiting for this lock to be granted后面的索引名、主键值(space id,page no,rec no) -
SQL 执行路径:对比两个事务的
mysql tables in use和index字段,确认是否走了不同索引
示例中若一个事务显示 index `idx_status_created`,另一个显示 index `PRIMARY`,说明它们本应走同一索引却因条件写法或统计信息偏差走了不同路径——这是典型优化点。
为什么加索引不等于解决死锁?重点看覆盖与顺序
加索引只是前提,真正影响死锁概率的是「是否索引覆盖」和「索引字段顺序是否匹配查询模式」。InnoDB 在 DELETE 时仍需回表(访问聚簇索引)校验可见性,如果 WHERE 条件涉及的列不在索引中,就会先走二级索引定位,再通过主键回查,这个过程可能引入额外锁区间。
正确做法是让索引「覆盖 DELETE 条件 + 避免回表」:
- 对
DELETE FROM t WHERE status = ? AND created_at > ?,建复合索引INDEX idx_status_created (status, created_at),而非分开建两个单列索引 - 若表有
version字段用于乐观锁,且DELETE中带AND version = ?,也应纳入该复合索引,否则可能触发额外主键扫描 - 避免在复合索引中把高区分度字段(如
id)放在前面,而把低区分度字段(如status)放后面——这会导致范围扫描放大锁粒度
验证方式:执行 EXPLAIN DELETE FROM t WHERE ...(5.6+ 支持),确认 key 字段是预期索引,且 rows 尽量小;同时检查 Extra 是否含 Using index(表示索引覆盖,无需回表)。
批量 DELETE 时怎么控制锁范围不爆炸
一次性删几万行,即使有索引,也可能因锁住过大 next-key 区间引发连锁死锁。更稳妥的方式是分批 + 显式排序:
- 用主键或自增 ID 分片:
DELETE FROM t WHERE id BETWEEN ? AND ? AND status = 'done',每次控制在 1000 行内 - 强制按主键升序删除,避免优化器选择其他索引导致扫描方向不一致:
DELETE FROM t WHERE status = 'done' ORDER BY id LIMIT 1000 - 在事务外做分页逻辑(比如用上一批最大
id作为下一批起点),而不是依赖LIMIT配合无序 WHERE —— 后者无法保证两次扫描的加锁顺序一致 - 避免在同一个事务里连续执行多个不同条件的
DELETE,尤其当它们可能交叉锁定同一间隙时
真正难处理的不是单条 DELETE,而是业务中那些「先 UPDATE 状态,再 DELETE 归档」的组合操作——这时必须确保两个语句走完全相同的索引路径,否则极易形成 A 等 B、B 等 A 的闭环。


















