安全删除海量数据的核心是避免巨型事务,应采用分批提交(delete...limit+commit+sleep)、主键范围删除或重建表替代直接delete。

直接用 DELETE 删海量数据,99% 的情况下会卡住业务、拖垮主从、撑爆 undo log——这不是操作问题,是设计反模式。安全删除的核心不是“怎么写 DELETE”,而是“怎么不让它变成一个巨型事务”。
为什么 DELETE ... WHERE 会锁死数据库
InnoDB 不是真删,而是先标记再 purge。一次删百万行,等于:逐行加行锁 + 每行写 undo log + 每行记 binlog(ROW 格式下)+ 每个二级索引同步更新。这些动作全挤在一个事务里,锁不释放、日志不刷盘、purge 线程追不上,结果就是:
- 其他会话对同一张表的 UPDATE/INSERT 被阻塞,表现为“Waiting for table metadata lock”或“Lock wait timeout exceeded”
- binlog 文件暴涨,从库重放慢,延迟飙升到小时级
- undo 表空间持续增长,可能触发磁盘告警甚至 OOM
- 即使加了索引,如果条件选择率高(比如删 80% 数据),MySQL 仍要扫描大量页,IO 压力不减
DELETE ... LIMIT 循环必须配 COMMIT 和 SLEEP
这是最易上手、风险最低的方案,但光写 DELETE ... LIMIT 1000 不够,漏掉下面任意一点都可能翻车:
- 每次执行后必须显式
COMMIT,否则事务一直开着,锁和 undo 都不释放 - 必须加
DO SLEEP(0.1)或应用层延时,否则连续高频 DELETE 会打满磁盘 IO,影响其他查询 -
LIMIT值建议设为 500–5000:太小(如 100)导致循环次数爆炸、网络/解析开销大;太大(如 10000)单次锁持有时间过长 - WHERE 条件必须走索引,否则每次
LIMIT都要全表扫,越删越慢 - 不要依赖
ORDER BY保证顺序——InnoDB 不保证物理顺序,ORDER BY id LIMIT 1000可能跳过某些行或重复删
按主键范围删除比 LIMIT 更稳,但有硬前提
当你要删的数据在主键(如 id)上天然连续时,用 WHERE id BETWEEN ? AND ? 替代 LIMIT,效率更高、无扫描偏移问题。但它只适用于:
- 删除条件能映射成主键区间(例如
create_time 且 <code>id和create_time强相关) - 表有自增主键或时间递增主键,且无大量中间空洞(否则范围会包含不该删的行)
- 必须提前查出边界:
SELECT MIN(id), MAX(id) FROM t WHERE create_time ,不能边删边算 - 每批删完后仍需
COMMIT+SLEEP,否则和普通 DELETE 无异
真正安全的删除,往往不用 DELETE
当你要删的是表中绝大多数数据(比如删 95%),DELETE 无论怎么分批,本质还是在“擦除旧数据”。更优解是绕开删除,直接保留要留的:
- 建新表
CREATE TABLE t_new LIKE t,只插保留数据:INSERT INTO t_new SELECT * FROM t WHERE create_time >= '2026-01-01' - 用
RENAME TABLE t TO t_old, t_new TO t原子切换,毫秒完成,业务无感 - 后台再
DROP TABLE t_old,让 InnoDB 异步清理,不影响在线服务 - 注意:必须确保新表索引、分区、字符集等与原表完全一致,否则切换后查询会变慢或失败
这个方法唯一难的点是,你得说服自己:不是所有删除都必须用 DELETE 实现。











