DELETE慢的根本原因是事务和锁机制导致日志暴涨与锁持有时间线性增长;应采用ORDER BY+LIMIT分批删除并休眠,或用分区DROP、临时表接力、TRUNCATE+INSERT等替代方案。

DELETE慢的根本原因不是数据多,而是事务和锁机制
直接执行 DELETE FROM table WHERE condition 在千万级以上表上,往往不是“慢”,而是卡死、锁表、日志暴涨。根本问题在于:InnoDB 每删一行就写一条 undo log,整个操作包在一个事务里,日志无法截断,锁持有时间随行数线性增长。哪怕条件命中索引,也无法绕过这个底层约束。
常见错误现象包括:9002(日志已满)、Lock wait timeout exceeded、主从延迟飙升、innodb_row_lock_waits 指标突增。这些都不是 SQL 写得不好导致的,而是单事务模型在大数据量下的必然表现。
分批删除必须带 ORDER BY + LIMIT/TOP + 休眠
只写 DELETE ... LIMIT 10000 或 DELETE TOP (10000) 是危险的。MySQL 和 SQL Server 都不保证无序删除的稳定性——可能漏删、重复删,或因执行计划变化导致批次重叠。
-
ORDER BY id(或时间字段)是刚需,确保每次删的是确定范围的连续主键 - 每批后加
SLEEP(0.1)(MySQL)或WAITFOR DELAY '00:00:00.1'(SQL Server),给 checkpoint 和日志截断留出窗口 - 用
@@ROWCOUNT = 0判断终止,别用EXISTS(SELECT 1 FROM ...),后者可能触发全索引扫描 - 起手建议批次大小为
5000~10000:太小(如 100)事务开销占比过高;太大(如 50000)易引发WRITELOG等待
替代方案比“优化 DELETE”更有效
当你要删掉 80% 以上的数据时,DELETE 本身就不该是首选。真正高效的解法是绕过 DML 流程:
- 分区表直接
DROP PARTITION:秒级完成,日志增长几乎为零,适合按时间字段(如log_time)分区的场景 - 临时表接力:先
CREATE TEMPORARY TABLE tmp_ids SELECT id FROM t WHERE condition LIMIT 10000,再DELETE FROM t WHERE id IN (SELECT id FROM tmp_ids),减少网络传输和锁粒度 - TRUNCATE + INSERT:保留少量数据时,建新表、INSERT 符合条件的数据、
RENAME TABLE切换,全程无 undo log,但需双倍磁盘空间
注意:分区和 TRUNCATE 都要求停写或应用层配合切换,不能在高并发写入场景下直接套用。
容易被忽略的配套动作
光调 SQL 不够,以下配置项不调整,分批删除照样会翻车:
- SQL Server 下确认恢复模式:若为
FULL,必须确保日志备份任务正在运行,否则分批也释放不了日志空间 - 临时关闭自动统计更新:
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS OFF,否则每批删完触发统计重建,等于又扫一遍表 - 禁用触发器:
DISABLE TRIGGER,避免额外日志和锁竞争,删完再ENABLE - 监控
tempdb使用率(SQL Server)或innodb_buffer_pool_wait_free(MySQL),排序或哈希操作挤占资源时,ORDER BY字段必须有索引
最常被跳过的其实是“按主键范围分片”——比如先查出最小/最大 id,再用 BETWEEN 循环删,比 LIMIT/OFFSET 稳定得多,也更容易中断和续跑。



















