分批删除可行但必须走索引,否则全表扫描导致越删越慢、主从延迟飙升、锁冲突加剧;应建索引或用主键范围删除,控制每批500–5000行并及时提交,配合归档与验证。

直接结论:用 DELETE ... WHERE ... LIMIT 分批删可行,但必须确保 WHERE 条件能走索引;否则每次 LIMIT 都要全表扫描,越删越慢,甚至拖垮主从同步。
为什么带 LIMIT 的 DELETE 会越来越慢
不是 LIMIT 本身有问题,而是 InnoDB 在执行 DELETE 时,先要定位目标行——这个“找”的过程如果无法利用索引,就会退化为全表扫描。一张几亿行的表,第一次删可能扫 10 万行才凑够 1000 条匹配记录;删到后面,可能要扫上百万行才能找到下一批。同时,每删一批都会产生大量 undo log 和 redo log,锁住对应范围的间隙(gap lock),进一步阻塞业务写入。
常见错误现象包括:
- 单条
DELETE执行时间从毫秒级涨到数秒甚至几十秒 - 主从延迟持续飙升,
Seconds_Behind_Master超过 1000 秒 - 业务接口出现大面积
Lock wait timeout exceeded
WHERE 条件必须命中索引,否则 LIMIT 形同虚设
假设你要删 create_time 的数据,但 <code>create_time 没建索引,那下面这句就是危险操作:
DELETE FROM orders WHERE create_time <p>它每次都要遍历全表去找前 1000 行,效率极低。正确做法是:</p>
- 确认
create_time字段已建索引:ALTER TABLE orders ADD INDEX idx_create_time (create_time); - 若该字段选择性差(比如大量重复值),优先考虑组合索引,例如
(create_time, status) - 更稳妥的是用主键驱动:先查出一批满足条件的最小
id,再按id范围删,例如:DELETE FROM orders WHERE id BETWEEN 100000 AND 101000 AND create_time
实际执行时的关键控制点
分批删除不是写一条 SQL 就完事,得控制节奏和副作用:
- 每批次数量建议设为
500–5000,太小(如 100)会导致循环次数过多、网络开销大;太大(如 50000)容易触发长事务和锁竞争 - 每次执行后必须
COMMIT,不能攒在同一个事务里——否则 undo log 爆涨,可能填满ibdata1 - 务必在业务低峰期执行,避免和高峰期写入冲突
- 删完建议跑一次
OPTIMIZE TABLE orders;回收碎片空间(注意:该操作会锁表,需评估停机窗口) - 脚本中要检查
rowcount == 0作为退出条件,防止无限循环
别忽略归档和验证环节
线上删数据最怕误删且不可逆。哪怕用了索引+LIMIT,也得提前做两件事:
- 删之前用
SELECT COUNT(*)和SELECT MIN(id), MAX(id)验证范围是否符合预期 - 对关键表,建议先用
INSERT INTO archive_orders SELECT ...把待删数据导出归档,再删原表 - 事件调度器(
EVENT)可自动化,但首次启用必须人工盯执行日志,确认每批都删了且没报错
真正难的从来不是“怎么写那条 SQL”,而是判断哪些索引有效、哪些范围安全、哪些锁会影响谁——这些细节不落地,再标准的 LIMIT 写法也会在凌晨两点把你叫醒。











