DELETE极易引发死锁,根本原因是RR级别下无差别加Gap/Next-Key锁(哪怕未命中记录),而INSERT所需插入意向锁与之互斥,多事务并发时易形成循环等待。

DELETE 操作极易引发严重死锁,根本原因不是语句本身“重”,而是它在 REPEATABLE READ 隔离级别下无差别加 Gap Lock 或 Next-Key Lock,哪怕一行都没删到,也会锁住索引间隙;而 INSERT、UPDATE 等操作要往同一间隙写入时,就会因锁互斥直接阻塞,稍有并发就形成循环等待。
DELETE 即使没命中记录也会加 Gap 锁
这是最反直觉也最危险的一点。比如执行 DELETE FROM t WHERE id = 105,而表中实际只有 id = 100 和 id = 110 两行,InnoDB 仍会锁定间隙 (100, 110) —— 不是因为它想删,而是 RR 级别下防止幻读的强制行为。
- 该锁与
INSERT所需的插入意向锁(insert intention lock)天然互斥 - 多个事务同时执行未命中的
DELETE,再各自尝试插入id = 105,立刻触发 A 等 B、B 等 A -
SHOW ENGINE INNODB STATUS里只显示 “waiting for insert intention lock”,但不会告诉你谁 hold 了 Gap 锁
非唯一索引 + 等值条件 = Next-Key Lock 全覆盖
很多人以为 DELETE WHERE status = 0 只锁匹配的行,其实只要 status 是普通索引(非唯一),InnoDB 就会对每个扫描到的索引项加 Next-Key Lock,即“记录锁 + 前一个间隙锁”。
- 例如索引值为
[0, 0, 1, 1, 2],删status = 1会锁住(0, 1]和(1, 2]两个区间 - 若并发事务分别按
status = 1和status = 2删除,又都试图插入新status = 1记录,极易路径交错死锁 - 用
EXPLAIN DELETE查看type:若是range而非ref或const,基本已进入高危锁模式
没索引 or 索引失效 → 全表扫描 + 行级 Next-Key 锁爆炸
当 WHERE 条件无法走索引(如 WHERE DATE(create_time) = '2025-01-01'),MySQL 退化为全表扫描,对每一行都加 Next-Key Lock,锁数量与表行数正相关。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 删 10 万行?可能瞬间持有 10 万把行锁 + 对应间隙锁,锁管理开销剧增
- 此时任意一个
INSERT或UPDATE只要涉及被锁范围,都会被阻塞 - 连接池中若复用了未提交事务的连接(如 Spring
@Transactional未退出),锁持续不释放,binlog/undo log 持续膨胀
分批删还卡住?大概率是 autocommit 关闭或 ORDER BY 缺失
加了 LIMIT 1000 仍卡死,90% 是事务控制出了问题,而非 SQL 本身。
- 查当前会话:
SELECT @@autocommit;,若返回0,说明后续所有DELETE都在一个长事务里 - 没
ORDER BY的分批删,InnoDB 不保证扫描顺序,同一批可能反复删前 1000 行,跳过后面数据 - 正确姿势:
DELETE FROM t WHERE status = 0 AND id > 12345 ORDER BY id LIMIT 1000,并建联合索引INDEX idx_status_id (status, id)
真正难处理的从来不是“删得多”,而是“删得看似没事却悄悄锁了一大片”。Gap 锁不显式出现在大多数监控视图里,也不报错,只在并发写入时突然爆发——查活跃事务、盯 performance_schema.data_locks 中的 LOCK_MODE = 'GAP',比等死锁日志更早发现问题。










