触发器不能直接用于按月归档,因其仅响应INSERT/UPDATE/DELETE事件,无时间感知能力;按月归档必须依赖EVENT+存储过程(MySQL)、pg_cron或外部cron(PostgreSQL)、SQL Server Agent(SQL Server)等定时调度机制,配合事务控制、动态表名、结构适配与锁机制实现。

触发器不能直接用于按月归档,这是关键前提
SQL 触发器(TRIGGER)是响应 INSERT/UPDATE/DELETE 的即时操作,它没有时间维度感知能力——你无法让一个触发器“在每月1号凌晨自动运行”。试图用 CREATE TRIGGER 实现“按月归档”属于典型的功能错配,强行写会导致逻辑断裂或根本不可用。
真正可行的归档路径:事件 + 存储过程 + 调度
按月归档必须依赖数据库的定时任务机制,把归档逻辑封装进存储过程,再由调度器驱动。不同数据库方案差异大,重点看实际可落地的组合:
- MySQL 5.7+:用
EVENT+PROCEDURE,需确认event_scheduler=ON(SHOW VARIABLES LIKE 'event_scheduler';) - PostgreSQL:用
pg_cron扩展(需安装)或外部 cron 调用psql -c "CALL archive_monthly();" - SQL Server:用 SQL Server Agent Job 调用
EXEC archive_monthly - SQLite:无内置调度,必须靠应用层或系统 cron 调用
sqlite3 db.db ".read archive.sql"
归档存储过程本身要严格区分“源表”和“归档表”,例如业务明细表 order_detail 归档到 order_detail_archive_202409,表名动态拼接需用 PREPARE(MySQL)或 EXECUTE format(...)(PostgreSQL),不能硬写死月份。
归档过程必须加锁与事务控制,否则会丢数据
常见错误是先 INSERT INTO ... SELECT 再 DELETE,中间若被其他写入打断,就会重复归档或漏删。正确做法是:
- 用单事务包裹整个归档动作(
BEGIN TRANSACTION/START TRANSACTION) - 对源表加
SELECT ... FOR UPDATE锁(MySQL/PostgreSQL 支持),或用WITH (TABLOCKX)(SQL Server)确保期间无并发修改 - 归档前用
WHERE create_time < DATE_SUB(CURDATE(), INTERVAL 6 MONTH)这类确定性条件,避免用NOW()导致跨月执行结果不一致 - 归档后检查
ROW_COUNT()或GET DIAGNOSTICS,失败时记录日志并回滚,不能静默吞掉异常
归档表结构要兼容历史数据,别盲目复制主键
归档表不是源表的镜像拷贝。容易忽略的点:
-
auto_increment主键在归档表中通常应改为普通INT,避免插入冲突;归档表主键建议用(id, archive_month)复合唯一约束 - 源表上的
ON UPDATE CURRENT_TIMESTAMP字段在归档后已无意义,归档表应去掉该属性,改用固定归档时间戳字段(如archived_at DATETIME) - 索引策略要重审:
create_time在归档表中大概率是高频查询条件,但status等业务字段可能不再需要索引,删掉能省空间 - 分区表(如 MySQL
PARTITION BY RANGE (TO_DAYS(create_time)))比按月建独立表更易维护,但要求 MySQL 5.7+ 且归档逻辑需适配分区管理命令
归档不是简单搬数据,核心是隔离冷热、保障一致性、留出追溯路径。调度时机、锁粒度、表结构演进这三点,任何一个没对齐,上线后就会在某个凌晨三点开始报错。

















