千万级数据删除不能直接用 DELETE FROM table WHERE...,否则会导致锁表、日志暴涨、IO打满、主从延迟;根本原因是InnoDB事务机制下每行删除需写undo/redo日志、更新索引、维护MVCC,且WHERE条件必须走有效索引避免全表扫描。

直接用 DELETE FROM table WHERE ... 删除千万级数据,基本等于给数据库埋雷——锁表、日志暴涨、IO打满、主从延迟,甚至拖垮整个服务。真要安全高效删,得绕开“单事务大删除”这个死胡同。
为什么 DELETE 会锁表且慢得离谱?
根本原因不是语句本身,而是 InnoDB(或 SQL Server)的事务机制:每删一行,都要写 undo/redo 日志、更新所有索引、维护 MVCC 版本链。删 500 万行 ≈ 500 万次随机 I/O + 索引分裂 + 锁住对应数据页。更糟的是,如果 WHERE 条件没走索引,就会全表扫描——你见过跑三天还没删完的 DELETE 吗?那八成是没索引。
- 务必确认
WHERE字段(如create_time、dt)有有效索引,且执行计划显示走了索引扫描(type=range或ref) - 避免在
WHERE中对字段做函数操作,比如WHERE DATE(create_time) 会失效索引 - SQL Server 要加
ROWLOCK提示,但别指望它绝对不升级锁;MySQL 则依赖引擎自动选行锁,前提是条件精准命中索引
分批删除必须控制节奏,不能只靠 LIMIT
LIMIT 是起点,不是终点。单纯循环 DELETE ... LIMIT 1000 在高并发下仍可能引发间隙锁冲突、主键范围跳跃、越删越慢(因 OFFSET 式逻辑隐含偏移扫描)。
- 优先用主键或时间字段范围切割,例如:
DELETE FROM orders WHERE id BETWEEN 1000001 AND 1010000,再递增区间 - 每次执行后加
SLEEP(0.1)(MySQL)或WAITFOR DELAY '00:00:00.1'(SQL Server),给 IO 和锁释放留出缓冲 - 监控
innodb_row_lock_waits(MySQL)或sys.dm_exec_requests中的wait_type(SQL Server),一旦发现锁等待飙升,立刻暂停 - 批次大小看表宽度:窄表(1KB/行)建议 500–2000,否则单批事务日志就超百 MB
比删除更快的其实是“不删”——分区表 + DROP PARTITION
如果你的删除条件天然按时间/业务周期分布(比如 create_time、dt),分区表是唯一能实现秒级清理的方案。它不走 DML,只是元数据操作。
- MySQL 示例:
ALTER TABLE events DROP PARTITION p202301;,执行时间通常 - SQL Server 需配合分区函数和方案,用
SWITCH+DROP组合,同样规避日志和锁压力 - 注意:已有大表改分区需在线 DDL(MySQL 8.0+
ALGORITHM=INSTANT可行,但非所有版本支持);若无法改结构,此路不通
删大部分数据时,重建表比逐行删更稳
当你要保留的数据 CREATE TABLE new LIKE old + INSERT SELECT + RENAME 是最可控的方式。它不产生 undo 日志、不锁原表全量、不触发索引逐行维护。
- 关键动作必须原子化:
RENAME TABLE old TO old_bak, new TO old;是瞬间完成的 - 旧表先不急着
DROP,留 24 小时观察业务是否正常,再异步清理 - 风险点在于磁盘空间——你需要双倍空间存新旧两份数据,执行前务必检查
df -h或sys.master_files - 应用层需确保切换窗口期写入停顿(或通过代理层灰度切流),否则会出现数据不一致
真正难的从来不是“怎么删”,而是判断该用哪条路径:有没有索引?删多少比例?能不能动表结构?业务能否接受短暂停写?这些决策点藏在日志增长曲线、锁等待堆栈、磁盘余量里,而不是某条 SQL 里。










