分批删除应使用 WHERE id > last_id ORDER BY id LIMIT N 控制范围,每批1000–5000行,加 SLEEP(0.1) 降低负载,且条件字段必须有索引;TRUNCATE 不适用带条件删除,因其重置自增ID、不触发触发器且受外键约束限制;DELETE 后空间不立即释放属正常,需 OPTIMIZE TABLE 或 ALTER TABLE 才能真正回收。

DELETE 用 LIMIT 分批删,别直接全表扫
直接 DELETE FROM table WHERE condition 删几百万行,大概率卡住业务——InnoDB 会锁住扫描到的所有行(甚至间隙),其他查询/更新全堵住。分批删的核心是让每次事务足够小,释放锁快,也让 MySQL 日志、内存压力可控。
实操建议:
- 用
WHERE id BETWEEN ? AND ?或WHERE id > last_id ORDER BY id LIMIT N控制范围,避免OFFSET越往后越慢 - 每批删 1000–5000 行较稳妥;太小(如 100)会导致事务太多、网络和解析开销上升;太大(如 5 万)容易触发锁升级或长事务报警
- 删完一批后加
SLEEP(0.1)(应用层控制),别让主库 CPU 和 I/O 突增,也方便 DBA 监控时看到节奏 - 务必在
WHERE条件字段上有索引,否则每次都是全表扫描 + 文件排序,分批也没用
为什么不能用 TRUNCATE 或 DROP 再重建
TRUNCATE TABLE 确实快,但它会重置自增 ID、清空全文索引、失效外键约束检查,且无法加 WHERE 条件——你要删的是“满足某条件的旧数据”,不是整张表。
常见错误现象:ERROR 1701 (42000): Cannot truncate a table referenced in a foreign key constraint,说明有外键依赖,TRUNCATE 直接失败。
使用场景限制:
-
TRUNCATE是 DDL,隐式提交,不能回滚;DELETE是 DML,可被事务包裹 - 如果表上有触发器,
TRUNCATE不触发DELETE触发器,逻辑可能漏掉 - 分区表可以用
ALTER TABLE ... DROP PARTITION快速清理历史分区,但前提是数据已按时间等维度做了分区
DELETE 后磁盘空间没释放?别慌,这是正常现象
InnoDB 删除行只是标记为“可复用”,不会立刻归还给操作系统。你看到 data_length 没变、df -h 磁盘占用照旧,不代表删失败了。
关键判断点:
- 查
SELECT COUNT(*)或SELECT TABLE_ROWS FROM information_schema.TABLES(后者是估算值,但趋势可参考)确认行数确实在下降 - 观察
innodb_data_file_path对应的 ibdata1 或独立表空间文件是否增长停滞,说明回收已在进行 - 真正释放空间要靠
OPTIMIZE TABLE(会锁表)或ALTER TABLE ... ENGINE=InnoDB(8.0+ 支持 ALGORITHM=INPLACE 的情况除外),但这两者本身代价很高,一般不推荐在线执行
更稳的方案:用主键范围 + 应用层循环控制
比纯 SQL 更可控的方式,是把删除逻辑搬到应用里,靠主键递增特性做游标式清理。比如 Python 脚本读取当前最大 id,每次删 WHERE id > ? AND id ,更新游标继续下一轮。
这样做的好处:
- 能精确控制每批耗时(比如限定单次不超过 200ms),超时自动跳过,避免拖垮数据库
- 可记录断点(最后成功删到的
id),中断后从断点续跑,不怕脚本崩了重头来 - 便于接入监控:上报已删行数、平均延迟、失败批次,而不是黑盒执行一条 SQL
- 注意:别用
SELECT MAX(id)做边界,高并发下可能漏数据;改用SELECT id FROM table WHERE condition ORDER BY id LIMIT 1 OFFSET N配合游标更安全


















