直接删千万级数据必然引发长事务和锁问题,必须分批处理;推荐主键范围删除,需确保索引有效、每批及时提交、添加合理延时,并验证结果。

直接用 DELETE FROM table WHERE ... 删千万级数据,几乎必然触发长事务、行锁升级、主从延迟飙升——这不是配置问题,是 InnoDB 引擎的底层行为决定的。必须绕过“单事务删全量”这个陷阱。
为什么 LIMIT 10000 还是会锁很久?
表面看加了 LIMIT 就安全,但实际执行时 MySQL 仍可能扫描大量无关行:
-
WHERE条件字段没索引?全表扫描 + 所有扫描行加间隙锁,哪怕只删 100 行,也锁住几万行 - 优化器选错执行计划(比如走错索引),导致每次
DELETE ... LIMIT 1000都从头扫,重复扫描已删过的范围 -
LIMIT不带ORDER BY,结果集无序,下一批可能又扫到刚删过的行,造成漏删或重删
真正可控的是“范围明确的扫描路径”,不是“删多少行”。
用主键范围分批删除(推荐首选)
适用于有自增 id 或时间递增主键的表,核心是把“删符合条件的数据”转化为“删某段 id 区间内符合条件的数据”。
操作步骤:
- 先查出要删的最小和最大
id:SELECT MIN(id), MAX(id) FROM logs WHERE create_time - 按固定步长(如 5000)切分区间:
DELETE FROM logs WHERE id BETWEEN 100000 AND 104999 AND create_time - 每次执行后
COMMIT,再DO SLEEP(0.1)控制 IO 压力
优势:走主键索引,扫描路径稳定;BETWEEN 条件天然可预测;断点续删容易(记录上一个 id 即可)。
用 INSERT + RENAME 替换整张表(适合删 90%+ 数据)
当你要删掉绝大部分数据(比如 1.6 亿删剩 250 万),DELETE 再怎么分批也慢——此时应该放弃“删”,改用“保留+替换”。
关键语句链:
CREATE TABLE logs_new LIKE logs;INSERT INTO logs_new SELECT * FROM logs WHERE create_time >= '2025-01-01';-
RENAME TABLE logs TO logs_old, logs_new TO logs;(毫秒级原子切换) -
DROP TABLE logs_old;(后台异步清理,不影响业务)
注意:必须确保新表结构完全一致(含索引、分区、外键约束),否则 RENAME 后业务查询会报错或性能骤降。
最容易被忽略的三个实操细节
再稳妥的方案,漏掉下面任一点都可能翻车:
- 每批删除后不
COMMIT,Undo 日志持续累积,最终触发磁盘满或事务超时 - 没在应用层或存储过程中加
DO SLEEP(0.1)(或等效延时),连续高强度 I/O 会拖垮整个实例,不只是这张表 - 删完不验证:
SELECT COUNT(*)看行数是否符合预期;别信df -h,InnoDB 不立刻归还空间给操作系统,data_length不变是正常现象
真正卡点不在 SQL 怎么写,而在每一批是否落在 InnoDB 的“轻事务窗口”里:索引可用、范围可控、提交及时、节奏可调。











