不能直接DELETE历史数据,因会引发行锁、间隙锁、MVCC版本链暴涨、binlog爆满及备份延长;应采用分区表+DROP PARTITION或RENAME TABLE等DDL秒级归档,而非真删。

为什么不能直接 DELETE 历史数据?
秒级 DELETE 大量历史行在生产库中基本等于自毁:InnoDB 会加行锁→间隙锁→甚至表级锁;MVCC 版本链暴涨拖慢所有查询;binlog 爆满影响主从同步;备份窗口被拉长。真实场景里,DELETE FROM orders WHERE created_at 跑十几分钟还卡住是常态。
归档不是“删得快”,而是“不真删”。核心思路是:把数据搬走,再用 DDL 切断关联——DDL(如 DROP PARTITION 或 RENAME TABLE)才是真正的秒级操作。
用分区表 + DROP PARTITION 实现秒级“归档删除”
前提是你的 MySQL ≥ 5.7 且使用 InnoDB,且业务时间字段(如 created_at)能支撑 RANGE 分区。别试图用非时间字段强行分区,后续维护成本爆炸。
实操要点:
- 建表时就按月/周分区,例如
PARTITION BY RANGE (TO_DAYS(created_at)),每个分区对应一个时间段 - 归档前先
INSERT INTO archive_orders SELECT * FROM main_orders WHERE created_at (走批量 INSERT,关掉 autocommit,设大bulk_insert_buffer_size) - 确认归档数据无误后,直接
ALTER TABLE main_orders DROP PARTITION p_2022_q4—— 这条语句毫秒完成,不锁表、不写 undo log - 注意:DROP PARTITION 后,该分区定义从
INFORMATION_SCHEMA.PARTITIONS消失,但磁盘文件(.ibd)不会立刻回收,需等 purge thread 清理;若想立即释放空间,跟上OPTIMIZE TABLE(但它是阻塞操作,慎用)
没分区?用 RENAME + TRUNCATE 替代
如果主表已上线多年、无法重建分区,又必须秒删,唯一靠谱路径是“换表法”。
步骤如下:
- 新建归档表
orders_2022_archive,结构与主表完全一致(含索引、字符集、ROW_FORMAT) - 用
INSERT INTO ... SELECT把历史数据导出(建议分批,每批 5–10 万行,避免事务过大) - 建好新主表
orders_new,只包含非历史数据(WHERE created_at >= '2023-01-01') - 停写一小段时间(或用
FLUSH TABLES WITH READ LOCK),执行原子性切换:RENAME TABLE orders TO orders_old, orders_new TO orders - 最后
TRUNCATE TABLE orders_old—— TRUNCATE 是 DDL,秒级,且重置 auto_increment
容易被忽略的三个硬伤
归档不是一劳永逸:
- 外键约束会拦住
DROP PARTITION和RENAME,必须提前ALTER TABLE ... DROP FOREIGN KEY,归档完再加回来(注意引用关系是否还成立) - 所有涉及历史数据的查询(比如报表、审计)必须改写为查归档表,否则结果缺失——别指望应用层自动路由
- 归档表的索引策略要独立设计:主表可能需要高频更新索引,归档表只需支撑只读查询,可删掉冗余索引、改用更紧凑的
KEY_BLOCK_SIZE
归档逻辑越简单,出问题时越容易回滚。最危险的操作不是慢,而是“以为删掉了,其实还在锁表、还在生成 binlog、还在拖垮从库”。










