AFTER/BEFORE触发器无法归档老数据,因其仅响应当前DML涉及的行,不扫描全表,无法按时间条件批量清理,强行使用将导致锁表、事务卡死或数据丢失。

不能用触发器安全归档过期历史数据并释放主表空间——它既不响应时间条件,也不支持批量清理,强行使用会导致锁表、事务卡死或静默丢失。
为什么AFTER INSERT/UPDATE触发器不能归档老数据
触发器只对当前DML语句涉及的行生效,不会主动扫描全表。你写AFTER INSERT,它只看到刚插入的那几行;写BEFORE UPDATE,它只拿到OLD.*那一行快照。想靠它把“所有create_time 的记录搬走”,语法上就报错,逻辑上也根本不会执行。
- MySQL中在触发器里
SELECT * FROM main_table WHERE create_time 会直接失败(不允许在触发器中查询本表) - 即使绕过限制(比如用临时表+预加载),每次INSERT都触发一次全表扫描,百万级表下写入延迟飙升
- 归档动作混在业务路径里,API响应从20ms变成800ms,监控毛刺明显
DELETE + TRUNCATE无法释放InnoDB磁盘空间
很多人以为DELETE FROM main_table WHERE create_time 完就结束了,其实只是把行标记为可复用,ibd文件体积不变,备份和主从延迟照旧。真正释放空间必须重建表结构。
-
OPTIMIZE TABLE main_table能回收空间,但会锁表、阻塞写入,且需双倍磁盘空间临时存放 -
TRUNCATE TABLE虽快,但只能清空整表,不满足“只删过期”的需求 - 分区表是唯一能秒级释放空间的方案:用
ALTER TABLE main_table DROP PARTITION p202401,物理删除整个分区文件
真正安全的归档流程:三步分离
把“识别”“搬运”“清理”拆到不同阶段,各自可控、可重试、可监控。
-
识别与搬运:用定时任务(MySQL
EVENT或应用层脚本)执行INSERT INTO archive_table SELECT * FROM main_table WHERE create_time ,带ORDER BY create_time保证顺序,每次只搬一批 -
校验与清理:搬运后查
archive_table和main_table的COUNT(*)是否匹配;确认无误再执行分批DELETE FROM main_table WHERE create_time ,每批后加WAITFOR DELAY '00:00:00.1'(SQL Server)或SLEEP(0.1)(MySQL)释放锁 -
空间回收:若主表已按
create_time分区,直接DROP PARTITION;否则用ALTER TABLE main_table ENGINE=InnoDB重建(低峰期执行,提前评估磁盘余量)
最容易被忽略的细节
归档表没建索引,后续查某条记录的历史版本要全表扫;主表create_time字段没索引,每次归档前的WHERE条件就变慢查询;事件调度器event_scheduler默认关闭,建完EVENT却从不运行——这些都不是“功能没写对”,而是上线前根本没验证过基础状态。










