分批删除比一次性删除快,因每次仅持锁毫秒级、undo可及时purge、binlog压力平滑;需用WHERE id > last_id ORDER BY id LIMIT N配合索引,每批1000–5000行,加SLEEP(0.1)控节奏。

直接用单条 DELETE 删除百万级以上数据,速度不会快——它会锁得更久、undo 膨胀更快、主从延迟飙升。真正提速靠的是「分批 + 控制节奏 + 释放资源」,不是堆参数。
为什么 LIMIT 分批删除比一次性删快得多
InnoDB 的 DELETE 不是“擦掉就完事”,它要逐行加锁、写 undo、更新索引、记 binlog。一次删 100 万行,等于连续持锁 + 持续写日志 10 分钟以上,期间其他查询可能全被堵住。
- 分批后每次只锁几百~几千行,锁持有时间从分钟级降到毫秒级
- 每批
COMMIT后 undo 立即可被 purge,避免 undo 表空间暴涨 - binlog 按批生成,主从传输压力更平滑,不会突然塞爆网络缓冲区
- MySQL 优化器对
LIMIT的执行计划更可控,避免全表扫描误判
batch_delete_by_limit 存储过程的关键参数怎么调
下面这个结构最常用,但参数不调好反而拖慢:
DELIMITER $$
CREATE PROCEDURE batch_delete_by_limit()
BEGIN
DECLARE affected_rows INT DEFAULT 1;
WHILE affected_rows > 0 DO
DELETE FROM large_table WHERE status = 'expired' ORDER BY id LIMIT 500;
SET affected_rows = ROW_COUNT();
COMMIT;
DO SLEEP(0.1);
END WHILE;
END$$
DELIMITER ;
-
LIMIT值选 500~2000:太小(如 100)导致循环次数爆炸,网络和解析开销占比上升;太大(如 10000)锁时间拉长,容易触发锁等待超时 -
ORDER BY必须有且走索引:否则每次LIMIT都要临时排序,性能断崖下跌;没索引就先建INDEX(status, id) -
SLEEP时间看负载:线上库 IO 已达 80%,建议设为0.3~0.5;空闲期可设0.05加速 - 不要省略
COMMIT:MySQL 存储过程默认在 AUTOCOMMIT=1 下运行,但显式写出来更安全,避免嵌套事务意外继承隔离级别
比 LIMIT 更快的场景:用主键范围切片删除
当你要删的数据在主键(如 id)上天然连续,比如“删掉所有 create_time 的记录”,这时 <code>LIMIT 反而低效——因为 MySQL 每次都要从头扫描找满足条件的前 N 行。
- 改用
WHERE id BETWEEN ? AND ?,配合已知的 min/max id 范围,走主键索引,I/O 成本直降 60% 以上 - 注意条件必须同时带上业务筛选(如
AND create_time ),防止误删新增数据 - 起始
id建议用SELECT MIN(id) FROM ... WHERE ...动态算,别硬编码,避免跳过新插入的旧数据 - 每批范围大小建议 5000~10000 行,比
LIMIT批次大但依然可控
最容易被忽略的性能杀手:索引维护
一张有 5 个二级索引的大表,删 100 万行,实际要写 500 万次索引页——这才是慢的根源,不是 SQL 写法问题。
- 如果业务允许,删数据前先
DROP INDEX临时索引(保留主键和关键查询索引即可),删完再CREATE INDEX - 特别警惕全文索引、GIS 索引、函数索引,它们的维护成本远高于普通 B+ 树索引
- 不要在删除过程中做
ANALYZE TABLE,统计信息更新会抢锁,等删完再跑 - 如果表有外键,确认是否真需要级联删除;能关则关(
SET FOREIGN_KEY_CHECKS = 0),删完再开
真正影响批量删除成败的,往往不是存储过程写得多漂亮,而是你有没有在删之前查清这张表的索引分布、有没有预估过 undo 增长量、有没有把 SLEEP 时间和业务低峰匹配上——这些细节不盯紧,再好的脚本也会在线上卡住。

















