删除数百万行必须分批,每批1000–5000行,用WHERE id > last_id ORDER BY id LIMIT N控制范围,id需为主键或有索引,禁用OFFSET,删后查MAX(id)续删,靠ROW_COUNT()确认实际影响行数。

DELETE 不能直接删数百万行——会锁表、打满 IO、拖垮主从。必须分批,且每批控制在 1000–5000 行之间,配合 COMMIT 和 SLEEP。
用 WHERE id > last_id ORDER BY id LIMIT N 控制范围
这是最稳妥的通用做法,尤其适合删除条件无法映射到连续 ID 范围的场景(比如按 status 或 create_time 删除)。
-
id必须是主键或有索引,否则每次都是全表扫描 + 文件排序,分批也白搭 - 别用
OFFSET:越往后偏移越慢,性能断崖式下跌 - 每次删完后用
SELECT MAX(id)获取下一批起点,避免漏删或重复删 -
ROW_COUNT()返回实际影响行数,就说明删完了,可退出循环
用 WHERE id BETWEEN min_id AND max_id 分段删(仅限主键连续)
如果要删的是旧数据,且 id 是自增主键、时间顺序大致一致,这个方式比 LIMIT 更快,因为不依赖排序和跳过逻辑。
- 先查出待删数据的最小和最大
id:SELECT MIN(id), MAX(id) FROM t WHERE create_time - 每次删一个固定跨度,比如
BETWEEN 1000000 AND 1004999,然后递增min_id - 务必加
AND create_time 双重保险,防止因并发插入导致误删 - 跨度建议设为 5000,太大容易触发锁升级;太小(如 100)会让网络往返和解析开销占比过高
为什么不能用 TRUNCATE 或 DROP/RECREATE
TRUNCATE TABLE 看起来快,但它根本不符合“批量删除”的前提——它不支持 WHERE 条件。
- 报错
ERROR 1701 (42000): Cannot truncate a table referenced in a foreign key constraint很常见,说明有外键依赖 - 会重置
AUTO_INCREMENT值,破坏业务连续性 - 不触发
DELETE触发器,可能漏掉关键清理逻辑(比如关联表级联更新) -
TRUNCATE是 DDL,隐式提交,无法回滚
删完空间没释放?这不是 bug,是 InnoDB 的正常行为
执行完 DELETE 后 data_length 不变、磁盘占用照旧,不代表失败——InnoDB 只是把页标记为“可复用”,不会立刻归还 OS。
- 确认是否真删了:查
SELECT COUNT(*)或TABLE_ROWS(后者是估算,但趋势可靠) - 真正回收空间得靠
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB,但这俩本身会锁表,生产环境慎用 - 如果表已分区,优先考虑
ALTER TABLE DROP PARTITION,这是秒级、无锁的替代方案
ORDER BY id LIMIT 要求 id 有索引,而 BETWEEN 方式要求 id 和时间字段强相关。没索引的字段上做条件删,再怎么分批也是徒劳。


















