大表删除应避免直接delete,而采用分批删除、truncate+insert或归档迁移。优先按主键/时间字段范围分片,禁用offset;保留少量数据时可用创建新表+重命名;归档优先于删除,分区表宜用drop partition。

大表删除慢,核心原因是单次 DELETE 操作会生成大量 undo/redo 日志、触发索引维护、锁住过多数据页,甚至引发长事务阻塞。直接 DELETE FROM table WHERE ... 在千万级以上表上极易卡住、超时或拖垮数据库。真正有效的解法不是“更快地删”,而是“更轻量、更可控地分批删”。
按主键/有序字段分片删除
避免全表扫描和随机 I/O,优先利用主键(如 id)或时间字段(如 create_time)做范围切割。每次只删固定区间的数据,确保每次操作快且可预测。
- 示例(按主键分批):
DELETE FROM orders WHERE id BETWEEN 1000001 AND 1010000 LIMIT 10000; - 先查出待删数据的最小/最大 id 范围,再用循环或脚本控制步长;不要用
OFFSET分页式删除(LIMIT N OFFSET M),越往后越慢。 - 若无合适有序字段,可建临时自增序号(如 MySQL 8.0+ 的
ROW_NUMBER()),但需注意性能开销,仅作兜底。
用 TRUNCATE + INSERT 替代(适合删大部分数据)
如果目标是保留少量数据(比如只留最近 3 个月),比逐条 DELETE 更高效:先将要保留的数据导出到新表,再重命名切换,最后删旧表。
- 步骤示意:
CREATE TABLE orders_new LIKE orders;INSERT INTO orders_new SELECT * FROM orders WHERE create_time >= '2024-07-01';RENAME TABLE orders TO orders_old, orders_new TO orders;DROP TABLE orders_old; - 优势:不走逐行 DML,无 undo 日志膨胀,不锁全表;缺点是需要双倍磁盘空间,且期间需停写或加应用层切换逻辑。
配合低峰期与限流策略执行
即使分批,也要防止对线上业务造成影响。不能只看“删完没”,更要关注“删的时候系统稳不稳”。
- 每批删除后加
SLEEP(0.1)(MySQL)或pg_sleep(0.1)(PostgreSQL),降低 I/O 和锁竞争压力; - 监控
innodb_row_lock_waits(MySQL)或pg_stat_activity(PG),发现锁等待飙升立即暂停; - 避开备份、统计信息收集、高峰期,最好在凌晨或业务低谷执行,并设置总超时(如最多运行 2 小时自动退出)。
必要时改用归档迁移方式
如果删除只是为“让老数据离线”,而非彻底丢弃,归档更安全、可追溯,也减少主库负担。
- 用
mysqldump --where="create_time 导出历史数据; - 导入到归档库或对象存储(如 Parquet + S3);
- 确认归档完整后,再用分批 DELETE 或 DROP PARTITION(若表已分区)清理主库。
- 分区表用户优先考虑
DROP PARTITION,毫秒级完成,零日志、零锁。










