mysql中delete...limit必须搭配order by主键,否则行为不可控且易致重复删、漏删;order by字段须有索引,禁用offset分页式删除;推荐每批1000–5000行并加应用层sleep(0.1s)缓冲。

直接执行 DELETE FROM table WHERE condition 删千万行,大概率拖垮主库、卡死从库、触发DBA紧急介入——这不是慢,是锁、日志、IO三重失控。
为什么 DELETE ... LIMIT 必须带 ORDER BY primary_key
MySQL 的 LIMIT 在无排序时行为不可控:每次扫描可能选不同行,导致重复删、漏删、进度无法预估。更危险的是,若条件字段没索引,会全表扫描 + filesort,分批也救不了性能。
- 必须显式写
ORDER BY id(或其他有索引的单调字段),确保每次从上一批末尾继续 -
id字段要有索引(通常是主键),否则ORDER BY本身就会变慢查询 - 禁止用
LIMIT offset, size(如LIMIT 10000, 1000),越往后偏移越慢,且容易跳过已删行
每批删多少行?LIMIT 1000 还是 LIMIT 5000?
批量大小不是越大越好,也不是越小越安全,得看你的锁粒度容忍和监控节奏。
- 推荐范围:1000–5000 行/批;低于 500 会显著增加事务数、网络往返和解析开销
- 高于 5000 容易触发 InnoDB 长事务告警(如
innodb_lock_wait_timeout或max_execution_time) - 若表有多个二级索引,建议往 1000–2000 靠拢——索引维护成本随行数非线性上升
应用层循环中要不要加 SLEEP?加多少?
不加 SLEEP 的循环删除,等于把数据库当队列消费机用:CPU 和 IO 毛刺尖锐、监控曲线拉满、DBA 第一时间收到告警。
- 在每次
DELETE ... LIMIT N后,应用层主动sleep(0.1)(100ms)是性价比最高的缓冲手段 - 不要依赖 MySQL 的
SLEEP()函数(它在服务端阻塞连接,浪费连接池资源) - 若业务低峰期允许,可延长到
sleep(0.3–0.5),让 purge 线程跟得上 undo 清理节奏
删完数据,磁盘空间为什么没变?
这是最常被误判为“删失败”的点:InnoDB 删除只是标记页内空间为“可复用”,不会立刻归还给操作系统。
-
SELECT COUNT(*)或information_schema.TABLES.TABLE_ROWS下降,说明逻辑删除成功 -
SHOW TABLE STATUS LIKE 'table_name'中的Data_length不变,完全正常 - 真正释放物理空间需
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB,但这两者本身是重量级操作,生产环境慎用
真正难的不是写对那条 DELETE,而是判断该不该走分批、要不要先删索引、有没有分区可用、以及——你是否清楚这次删除会把 binlog 写满多少 GB。











