直接 delete where create_time 未指定具体条件(如比较操作符和时间值),语法不完整,无法执行删除操作。

为什么直接 DELETE WHERE create_time
因为 MySQL 在执行该语句时,会逐行扫描、逐行加锁、逐行更新所有相关索引(包括二级索引),同时生成大量 undo/redo 日志。千万级表上,单次操作可能持续数分钟甚至更久,期间阻塞写入、拖慢主从同步、触发超时告警。
按时间范围分段删除的实操要点
核心是把「全表扫描 + 全索引维护」拆成多个小事务,每次只触达局部数据页和少量索引项。但不能简单套用 LIMIT 或 OFFSET,必须结合有序字段做范围切割:
- 优先使用带索引的时间字段(如
create_time)或主键(如id)作为分段依据,确保每次WHERE条件能走索引,避免全表扫描 - 起始值不能硬编码,例如删 2023 年前日志,应先查:
SELECT MIN(id) FROM logs WHERE create_time ,再以该值为起点分批 - 步长建议设为
5000~10000:太大会导致单次事务过长、锁持有时间高;太小则网络往返和事务提交开销占比上升,实际吞吐反而下降 - 每次删除必须保留原始业务条件,例如:
DELETE FROM logs WHERE id BETWEEN 1000 AND 2000 AND create_time ,否则跨分区时可能误删新数据
比“分段 DELETE”更优的替代方案
如果删除条件天然按时间归档(如日志、订单、监控数据),真正省事又安全的做法是提前建好 RANGE 分区,后续删旧数据就变成秒级元数据操作:
- 建表时按
create_time或YEAR(create_time)做RANGE分区,例如每个分区存一年或一月数据 - 确认分区已生效后,删 2022 年数据只需:
ALTER TABLE logs DROP PARTITION p2022_01, p2022_02, p2022_03 - 该操作不走 DML 流程,不生成 undo 日志,不触发索引更新,也不锁数据行——本质是删除物理文件+清理元数据
- 注意:已有大表添加分区需重建(
ALTER TABLE ... PARTITION BY ...),代价较高,适合新表规划阶段介入
容易被忽略的关键细节
很多人以为只要加了 LIMIT 就算“分批”,却忽略了执行计划是否真的高效。常见陷阱包括:
-
ORDER BY+LIMIT组合在无覆盖索引时会触发 filesort,性能陡降;应确保WHERE条件字段与排序字段共同构成联合索引 - 未给
create_time字段建索引,或索引失效(如对字段做函数操作:DATE(create_time) ),导致每次分段仍全表扫 - 在从库上执行分批删除时,若主从延迟大,
MIN(id)查询结果可能滞后,造成漏删或重复删,建议在主库查范围、从库仅执行删除 - 事务隔离级别影响明显:在
REPEATABLE READ下,大范围DELETE可能触发间隙锁放大,而READ COMMITTED能缓解但需确认业务兼容性











