归档前必须确认WHERE条件能走索引、主键约束完整、事务隔离级别适配;本质是逻辑与物理分离,确保在线业务不可见不可改,且不锁表不拖慢查询。

归档前必须确认的三件事:WHERE 条件、主键约束、事务隔离级别
逻辑与物理分离的本质,不是“把旧数据挪走”,而是确保在线业务查不到、改不了归档数据,同时归档过程不锁表、不拖慢线上查询。很多人一上来就写 DELETE 或 INSERT INTO ... SELECT,结果卡住订单库两小时。
实操建议:
-
WHERE条件必须能走索引——比如用created_at 时,<code>created_at字段要有单列索引或复合索引的最左前缀;否则全表扫描 +LOCK IN SHARE MODE会拖垮并发 - 目标归档表必须有和原表一致的主键/唯一约束,否则后续应用层按 ID 查询会命中空值或重复,且无法用
REPLACE INTO或INSERT IGNORE安全回补 - 归档语句务必放在
READ COMMITTED或更低隔离级别下执行;在REPEATABLE READ下大范围SELECT FOR UPDATE可能触发间隙锁,锁住本不该动的行
用 INSERT … SELECT + DROP PARTITION 实现秒级物理分离(MySQL 5.7+)
分区表不是噱头,是唯一能真正“物理分离”且不重建索引的方案。前提是你的归档维度(如时间)天然适合 RANGE 分区,且业务允许按月/季度切分。
常见错误现象:直接 ALTER TABLE t DROP PARTITION p2021 后发现应用报错 ERROR 1517 (HY000): Cannot remove all partitions, use DROP TABLE instead——说明你没预留 MAXVALUE 分区兜底。
实操建议:
- 建表时必须带
PARTITION BY RANGE (TO_DAYS(created_at)),并始终保留一个PARTITION p_max VALUES LESS THAN MAXVALUE - 归档操作分两步:先
INSERT INTO archive_t SELECT * FROM main_t WHERE created_at (加 <code>LOW_PRIORITY降低对线上影响),再ALTER TABLE main_t DROP PARTITION p2021 - DROP PARTITION 是 DDL,但 MySQL 5.7+ 对其做了优化,只要不涉及二级索引重建,耗时基本是常数级;但注意 binlog 里它仍是一条完整语句,主从延迟可能突增
逻辑分离靠视图 + 行级安全策略(PostgreSQL 9.5+ / MySQL 8.0.16+)
如果不能改表结构、又不想让应用感知归档状态,“逻辑分离”就得靠数据库原生访问控制。不是简单建个视图,而是让同一张表对不同角色返回不同行集。
使用场景:审计系统要查全部历史,客服系统只能查近 180 天,且两者共用同一套 ORM 模型。
实操建议:
- PostgreSQL 开启 RLS:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY,然后CREATE POLICY policy_recent_only ON orders FOR SELECT USING (created_at >= CURRENT_DATE - INTERVAL '180 days') - MySQL 8.0+ 需配合角色:
CREATE ROLE 'customer_service'; GRANT SELECT ON sales.orders TO 'customer_service';,再用CREATE SQL SECURITY DEFINER VIEW orders_recent AS SELECT * FROM orders WHERE created_at >= DATE_SUB(NOW(), INTERVAL 180 DAY) - 注意:RLS 策略对
COPY、pg_dump、管理账号(如postgres)默认不生效;MySQL 视图无法被UPDATE或DELETE,除非定义为可更新视图且满足严格条件
归档后必做的四件事:统计更新、索引瘦身、查询路径验证、binlog 清理窗口
很多人删完数据就以为结束了,结果第二天发现报表变慢、监控告警频发、从库延迟飙升——因为归档只是开始,不是终点。
实操建议:
- 立刻执行
ANALYZE TABLE main_t(MySQL)或VACUUM ANALYZE main_t(PostgreSQL),否则优化器仍按旧统计信息生成执行计划,可能误选全表扫描 - 检查归档表是否有必要保留全部索引;通常归档表只读,可删掉高频更新类索引(如
status、updated_at),只留查询主键和时间范围用的索引 - 用
EXPLAIN FORMAT=TREE(MySQL 8.0)或EXPLAIN (ANALYZE, BUFFERS)(PostgreSQL)验证关键查询是否真的走到了新分区或新统计分布,而不是还在扫描已删数据的页 - 归档产生的大量
DELETEbinlog 会撑爆磁盘;MySQL 要确认binlog_expire_logs_seconds设置合理,PostgreSQL 要检查max_wal_size和归档进程是否及时消费
最难的从来不是怎么把数据搬走,而是怎么让所有依赖它的查询、备份、同步、监控,在不动代码的前提下,自动适应这个“少了一块”的新状态。

















