直接DELETE千万行会引发锁争用、日志膨胀、主从延迟等问题;应分批删除,以主键或时间字段为游标推进,每批控制在0.5–2秒内提交,并用checkpoint表实现断点续删。

为什么不能直接 DELETE FROM table WHERE ... 删除千万行?
直接执行大范围 DELETE 会一次性申请大量锁(尤其是 InnoDB 的行锁升级为表锁或间隙锁),阻塞所有读写;同时事务日志(redo/undo)急剧膨胀,可能触发磁盘满、超时或主从延迟飙升。MySQL 默认事务隔离级别下,未提交的删除操作还会让 SELECT ... FOR UPDATE 或长事务持续等待。
实操建议:
- 先用
SELECT COUNT(*)确认待删数据量,避免误判 - 检查该表是否有外键引用、触发器或复制过滤规则,这些会进一步拖慢删除速度
- 在业务低峰期操作,并提前通知 DBA 监控
innodb_row_lock_waits和Threads_running
分批删除的核心控制逻辑怎么写?
关键不是“每次删多少”,而是“如何精准定位下一批”——避免 LIMIT 配合无序 ORDER BY 导致漏删或重复删。推荐用主键/自增 ID 或时间字段做游标推进:
DELETE FROM orders WHERE created_at <p>说明:</p>
-
OFFSET不适用于超大偏移,所以更稳的方式是记录上一批最大id,下次WHERE id - 必须带
ORDER BY,否则LIMIT行为不可预测 - 每批删除后加
SLEEP(0.1)(存储过程中可用DO SLEEP(0.1))缓解 I/O 压力
事务提交频率怎么设才不崩也不慢?
每删 1000 行就 COMMIT 是常见误区:太频繁会放大事务开销;太稀疏(如 10 万行一提交)又可能 undo 日志撑爆或回滚耗时过长。实际应按“单次事务执行时间”而非行数来控制:
- 目标单次事务控制在 0.5–2 秒内,可通过
BENCHMARK或测试环境压测估算 - 使用
SELECT UNIX_TIMESTAMP(NOW(3))记录起始毫秒,在循环中判断是否超时 - 避免在事务内做
SELECT COUNT(*)这类全表扫描,改用EXPLAIN预估或采样统计 - 若表有二级索引,删除时会同步更新索引页,此时每批 5000 行比 10000 行更稳
存储过程里怎么安全终止和断点续删?
过程跑一半失败,不能靠人工查最后删到哪——得把断点存起来。建议建一张轻量控制表:
CREATE TABLE delete_checkpoint ( table_name VARCHAR(64), max_id BIGINT UNSIGNED, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (table_name) );
然后在存储过程中:
- 每次成功提交后,用
REPLACE INTO delete_checkpoint更新max_id - 启动时先查该表,作为初始
WHERE id 条件起点 - 加
DECLARE EXIT HANDLER FOR SQLEXCEPTION捕获异常并记录错误码到日志表 - 不要依赖
@@ROW_COUNT判断是否删完,而应查SELECT COUNT(*) FROM ... WHERE ... AND id > last_max_id
真正麻烦的是索引碎片和统计信息滞后——删完别忘了 ANALYZE TABLE 和评估是否需 OPTIMIZE TABLE,后者在大表上会锁表,要单独安排窗口。










