千万级表直接DELETE易致日志暴涨、锁表、主从延迟甚至OOM;应分批删除(主键范围切片+显式COMMIT+限流)或重建表(导出保留数据+原子替换)。

大表 DELETE 为什么卡死或失败
直接对千万级以上的表执行 DELETE FROM table WHERE ... 极大概率导致事务日志暴涨、锁表时间过长、主从延迟飙升,甚至触发 OOM 或被 DBA 杀掉。根本原因在于:InnoDB 默认逐行加锁 + 记录完整 undo log,且整个语句是一个大事务,回滚段压力巨大。
常见现象包括:Lock wait timeout exceeded、Query execution was interrupted、从库 SQL 线程长时间 Waiting for table metadata lock。
- 别用单条
DELETE清理超过 10 万行的数据 - 避免在业务高峰期执行,尤其不要在主库上直接删
- 确认表有合适索引——
WHERE条件字段必须走索引,否则会全表扫描+锁全表
分批删除的可靠写法(MySQL)
核心是控制每次删除的行数、显式提交、避开长事务。推荐用主键范围或自增 ID 切片,比 LIMIT 更稳定(LIMIT 在高并发更新下可能跳过或重复)。
示例:删除 orders 表中创建时间早于 2022 年的记录
SET @batch_size = 5000; SET @low_id = 0; SET @high_id = 0;WHILE @high_id < (SELECT MAX(id) FROM orders WHERE created_at < '2022-01-01') DO SELECT MIN(id) INTO @low_id FROM orders WHERE id > @high_id AND created_at < '2022-01-01' LIMIT 1;
IF @low_id IS NULL THEN LEAVE; END IF;
SELECT MIN(id) INTO @high_id FROM orders WHERE id >= @low_id AND created_at < '2022-01-01' ORDER BY id DESC LIMIT 1;
DELETE FROM orders WHERE id BETWEEN @low_id AND @high_id AND created_at < '2022-01-01';
COMMIT; DO SLEEP(0.1); -- 控制节奏,减轻主从压力 END WHILE;
- 每次只删一个连续 ID 段,避免
LIMIT的不确定性 -
COMMIT必须显式调用,不能依赖自动提交(存储过程默认 autocommit=0) -
DO SLEEP(0.1)是关键缓冲,防止瞬间 IO 和复制压力打满 - 如果表无主键或主键不连续,改用时间字段分段(但需确保该字段有索引且值唯一性足够)
更安全的替代方案:改名 + 重建
当要删除的是历史归档数据(比如删掉 90% 以上),物理重建比逻辑删除更快、更干净,且几乎不锁原表。
模板采用响应式设计,自动适应手机,电脑及平板显示;满足单一店铺外卖需求。功能:1.菜单分类管理2.菜品管理:菜品增加,删除,修改3.订单管理4.友情链接管理5.数据库备份6.文章模块:如:促销活动,帮助中心7.单页模块:如:企业信息,关于我们更强大的功能在开发中……安装方法:上传到网站根目录,运行http://www.***.com/install 自动
步骤本质是「把要保留的数据导出 → 建新表 → 导入 → 原子替换」:
CREATE TABLE orders_new LIKE orders; INSERT INTO orders_new SELECT * FROM orders WHERE created_at >= '2022-01-01'; RENAME TABLE orders TO orders_old, orders_new TO orders;
- 全程原表
orders只在最后RENAME那一瞬不可写(毫秒级),业务无感 - 要求磁盘空间充足——临时表 + 原表共存期间占用双倍空间
- 外键、触发器、注释等需手动迁移,
SHOW CREATE TABLE可辅助 - 如果表有全文索引或 JSON 列,注意 MySQL 版本兼容性(5.7+ 对
RENAME支持更稳)
PostgreSQL 怎么办:用 DELETE ... WITH 分事务
PG 没有 LIMIT 在 DELETE 中的语法,也不能在单语句里用变量,推荐用 CTE + WITH 循环配合应用层控制。
典型做法是在应用代码里循环执行:
DELETE FROM logs WHERE id IN ( SELECT id FROM logs WHERE created_at < '2022-01-01' ORDER BY id LIMIT 10000 );
- 每次只删 10k 行,应用层判断
rowcount == 0退出循环 - 务必在
created_at和id上建复合索引:CREATE INDEX idx_logs_time_id ON logs(created_at, id) - 避免用
OFFSET分页,深度分页性能断崖式下降 - 如果启用了
hot_standby_feedback = on,长事务可能导致备库查询被阻塞,所以每批必须及时提交
真正难的不是写出能跑的语句,而是判断「此刻该用分批删、还是重建、还是切归档库」——这取决于剩余数据比例、磁盘余量、主从延迟容忍度,以及你有没有权限停写。漏掉任一条件,都可能让操作从优化变成事故。









