DELETE归档触发器必须用BEFORE而非AFTER,因AFTER时原记录已物理删除;MySQL/PostgreSQL需在BEFORE中访问OLD行,SQL Server需用INSTEAD OF或OUTPUT替代;归档表结构须严格对齐主表,含字段名、类型、长度、NULL性及时间戳/json等特殊类型。

DELETE 触发器必须用 BEFORE 而不是 AFTER
因为 AFTER DELETE 触发时原记录已从主表物理删除,无法读取完整字段值——归档依赖的就是被删行的原始数据。MySQL 和 PostgreSQL 都要求用 BEFORE DELETE 才能访问 OLD 行变量。
- MySQL 中:
BEFORE DELETE可直接读OLD.column_name,写入归档表 - PostgreSQL 中:
BEFORE DELETE的触发器函数需返回OLD(否则 DELETE 会被取消) - SQL Server 不支持直接在
INSTEAD OF DELETE外访问被删行,得用临时表或 OUTPUT 子句替代,本质不是标准 DELETE 触发器
归档表结构必须和主表严格对齐
字段名、类型、长度、NULL 性都得一致,否则插入会失败或截断。特别注意时间戳字段:主表用 datetime,归档表不能用 timestamp(MySQL 中后者有自动更新行为);主表有 json 字段,归档表也得是 json,不能降级为 text。
- 建议用
CREATE TABLE archive_table LIKE main_table(MySQL)或SELECT * INTO archive_table FROM main_table WHERE 1=0(SQL Server)初始化结构 - 追加归档时间字段时,别用默认值
NOW(),应显式在触发器里赋值:archived_at = NOW() - 主表有自增主键,归档表该字段要改为普通
INT,否则 INSERT 会冲突
单行 DELETE 和批量 DELETE 的行为差异
触发器对每一行单独触发,不是对整个语句触发。所以 DELETE FROM orders WHERE status = 'canceled' 删除 1000 行,触发器会执行 1000 次——这会显著拖慢执行速度,且可能触发锁等待。
- 高并发场景下,避免在触发器里做复杂逻辑(如跨库写入、调用存储过程)
- 归档表记得建索引:至少在
archived_at和原主键上建索引,否则后续查归档数据变慢 - 如果业务允许,优先考虑用定时任务 +
INSERT ... SELECT+DELETE分两步归档,比触发器更可控
MySQL 中触发器无法回滚主事务的隐含风险
MySQL 的触发器里抛出异常(比如归档表插入失败),不会自动回滚当前 DELETE 语句——它只会让触发器退出,DELETE 继续执行,造成“数据丢了但没归档”的静默丢失。
- 必须手动检查归档插入结果:
IF ROW_COUNT() = 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Archive insert failed'; END IF; - PostgreSQL 更可靠:触发器函数返回 NULL 会中止 DELETE,但前提是函数声明为
RETURNS trigger并正确返回OLD - 测试时一定要模拟归档表满、磁盘写满、字段超长等边界情况,光看“删除成功”不等于归档成功
归档逻辑越靠近数据层,越容易漏掉异常分支;真正上线前,得用真实 DELETE 流量压测,而不是只验证单条语句。










