直接DELETE FROM table WHERE ...会卡死,因InnoDB需为每行更新所有二级索引、undo/redo log及MVCC版本链,引发高IO与buffer pool压力;分批删除必须加ORDER BY主键防漏删或重复删。

为什么直接 DELETE FROM table WHERE ... 会卡死?
因为 InnoDB 对每行删除都要更新二级索引(非主键索引),如果表有 5 个二级索引,删 10 万行 = 实际写入 50 万次索引页。再加上 undo log、redo log、MVCC 版本链维护,磁盘 IO 和 buffer pool 压力瞬间拉满。常见现象是 SHOW PROCESSLIST 里状态长期卡在 updating 或 end,同时 innodb_row_lock_time_avg 暴涨。
分批删除必须加 ORDER BY 主键
不加 ORDER BY 的 LIMIT 删除,在高并发或数据持续写入时极易漏删或重复删——MySQL 优化器可能每次选不同物理位置的行,尤其当 WHERE 条件匹配大量数据且无有效索引时。正确写法是:
DELETE FROM logs WHERE create_time < '2023-01-01' ORDER BY id LIMIT 1000;
-
ORDER BY id确保每次从最小未删 ID 开始扫,逻辑可预测 - 必须确保
id是主键或有覆盖索引,否则ORDER BY会触发 filesort,反而更慢 - 若主键不连续(比如用 UUID),优先改用主键范围删除,见下一条
主键范围删除比 LIMIT 更稳,但要注意边界条件
适用于自增 id 或时间递增主键。核心是避免 LIMIT 的偏移扫描开销,也规避了 ORDER BY 排序成本。典型错误写法:
-- ❌ 错误:没加 WHERE 条件过滤,会删错数据 DELETE FROM logs WHERE id BETWEEN 1000 AND 2000;
正确写法要双重约束:
DELETE FROM logs WHERE id BETWEEN 1000 AND 2000 AND create_time < '2023-01-01';
- 必须保留原始业务条件(如
create_time ),否则跨范围时可能误删新数据 - 起始值不能硬编码
0,应先查SELECT MIN(id) FROM logs WHERE create_time - 每次步长建议 5000~10000,太大仍会锁太久;太小则网络往返和事务开销占比上升
真正省事又安全的方案:分区表 + DROP PARTITION
如果你的删除条件天然按时间(如日志表按月归档),直接建 RANGE 分区,后续删旧数据就是秒级操作,完全绕过索引维护压力:
ALTER TABLE logs DROP PARTITION p2022_01;
前提是:
- 表已按
log_time建 RANGE 或 LIST 分区,且分区键上建了索引 -
DROP PARTITION不走 DML 流程,不写 undo log,不触发二级索引更新 - 注意:MySQL 8.0+ 支持
EXCHANGE PARTITION做在线迁移,但DROP本身不可回滚
分区不是银弹——它要求业务删除模式高度规律,且建表初期就得规划好;临时补救时,强行重构成分区表代价远高于分批删。


















