不加索引的DELETE会全表扫描并逐行加X锁及间隙锁,等效锁表且易死锁;分批删除需配合索引精准定位范围,用id > last_id+ORDER BY确保可控锁粒度与避免重复扫描。

为什么大表DELETE不加索引就容易锁死
因为InnoDB在执行 DELETE 时,会对**所有扫描到的行(含间隙)加X锁或GAP锁**,而不仅限于最终删掉的那些。如果WHERE字段没索引,MySQL只能全表扫描——几千万行挨个判断、逐行加锁、写undo/redo、维护MVCC链,CPU和I/O瞬间拉满,事务长时间不提交,其他查询就被堵死。
分批 + 索引如何实际降低锁压力
关键不是“分批”本身,而是**用索引把扫描范围收窄到可控区间,再靠边界值(如 id > last_id)避免重复扫描**:
- 有索引的前提下,
WHERE create_time 1000000能走联合索引或主键索引,每次只锁定几千行,而不是全表 - 每批
COMMIT后,这批锁立刻释放,undo log可被清理,MVCC版本链变短 - 配合
DO SLEEP(0.1)或控制batch_size(建议5000~10000),能错开IO峰值,避免从库复制延迟雪崩 - 别用
LIMIT配OFFSET:比如DELETE ... LIMIT 1000 OFFSET 100000,MySQL仍要跳过前10万行,锁照样打满
容易被忽略的隐性锁陷阱
即使加了索引,以下情况仍可能锁表:
-
autocommit=0但忘记手动COMMIT:锁会一直挂着,直到会话断开 - WHERE条件字段有索引,但优化器因统计信息过期选了全表扫描:执行前先
EXPLAIN DELETE ...确认是否走了索引 - 用了
ORDER BY却没对应索引:MySQL可能临时文件排序,放大锁范围 - 在从库上直接删:主从延迟导致同一行在主库已删、从库还没同步,触发唯一键冲突或主键缺失报错
真正省事的替代方案:分区表 + DROP PARTITION
如果你的删除逻辑天然按时间切分(比如日志表按月归档),建 RANGE 分区后,删旧数据就是 DROP PARTITION p202401,不走索引、不写undo、不加行锁——秒级完成。但注意:分区字段必须是WHERE条件的一部分,且不能和主键冲突(例如主键含 id,分区字段就不能只用 create_time,得组合成联合主键)。


















