直接delete where慢是因为innodb逐行更新二级索引、写undo/redo日志、维护mvcc版本链,若表有4个二级索引,删10万行≈40万次索引页写入+大量日志io;不加order by的limit删除易漏删或重复删。

为什么直接 DELETE WHERE 会慢到卡住
不是语句写错了,是 InnoDB 在“认真干活”:每删一行,都要更新所有二级索引、写 undo/redo log、维护 MVCC 版本链。如果表有 4 个二级索引,删 10 万行 ≈ 实际写入 40 万次索引页 + 大量日志 IO。常见现象是 SHOW PROCESSLIST 里状态卡在 updating 或 end,innodb_row_lock_time_avg 暴涨,主从延迟飙升。
必须加 ORDER BY 主键的 LIMIT 删除
不加 ORDER BY 的 LIMIT 删除,在数据持续写入或高并发下极易漏删或重复删——MySQL 优化器每次可能选不同物理位置的行,尤其当 WHERE 匹配行数多且无有效索引时。
DELETE FROM logs WHERE create_time 是安全基线写法-
id必须是主键,或至少是带覆盖索引的字段;否则ORDER BY会触发filesort,反而更慢 - 避免用
OFFSET(如LIMIT 10000,1000),越往后扫描成本越高
主键范围删除比 LIMIT 更稳,但边界不能错
适用于自增 id 或时间递增主键。它绕开排序和偏移扫描,IO 更线性,锁范围更可控。
- 错误写法:
DELETE FROM logs WHERE id BETWEEN 1000 AND 2000—— 缺少原始业务条件,可能误删新数据 - 正确写法:
DELETE FROM logs WHERE id BETWEEN 1000 AND 2000 AND create_time - 起始值别硬编码
0,先查:SELECT MIN(id) FROM logs WHERE create_time - 步长建议 5000~10000;太大仍会锁太久,太小则事务开销占比上升
真正省事又安全的方案:分区表 + DROP PARTITION
如果你的删除条件天然按时间划分(比如日志表按月分区),这是唯一能秒级完成、零索引压力、不阻塞查询的方案。
- 前提:表已按
log_time建RANGE分区,且分区键上有索引 - 执行:
ALTER TABLE logs DROP PARTITION p2022_01 - 注意:
TRUNCATE不适用——它不支持WHERE,会重置自增 ID,且受外键约束限制;DROP PARTITION则不受这些影响
最容易被忽略的一点:删完后 data_length 没变、磁盘占用没降,不代表失败。InnoDB 只是标记空间“可复用”,要真正释放给操作系统,得靠 OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB,但这俩本身代价极高,线上慎用。











