必须用 before delete 触发器,因其在删除前执行,可确保归档成功后再物理删除,避免数据丢失;after delete 因原行已移除,归档失败则无法挽回,且所有数据库均要求归档表字段严格对齐、显式列名插入,大表需改用分批定时归档。

必须用 BEFORE DELETE 触发器,不能用 AFTER —— 否则归档失败时数据已丢,无法补救。
BEFORE DELETE 是唯一安全的选择
触发器类型选错,整个回收站机制就形同虚设。AFTER DELETE 触发时,原行早已从主表物理移除,哪怕归档语句因字段不匹配、约束冲突或权限问题失败,你也拿不回那条数据;而 BEFORE DELETE 在删除动作发生前执行,归档成功才允许删,还能在触发器里加 IF 判断做拦截(比如禁止删除特定状态的记录)。
- MySQL 和 PostgreSQL 原生支持
BEFORE DELETE,语法直接、语义清晰 - SQL Server 不支持
BEFORE,必须用INSTEAD OF DELETE+ 手动INSERT INTO ... SELECT+DELETE FROM三步组合,且顺序不能颠倒 - 千万别信“
AFTER也能用OLD”这种说法——能取到OLD不代表逻辑安全,归档失败就是单点故障
归档表结构必须显式对齐,别偷懒写 SELECT OLD.*
用 SELECT OLD.* 插入到归档表,是线上事故高发操作。归档表若比原表多一个 archived_at DATETIME DEFAULT CURRENT_TIMESTAMP 字段,MySQL 直接报 Column count doesn't match value count;如果少一个非空字段,插入失败,整条 DELETE 就卡住。
- 归档表业务字段(含类型、NULL 属性、长度)必须与原表完全一致
- 额外至少加两个字段:
archived_at DATETIME NOT NULL(记录归档时间)、archived_by VARCHAR(64)(可填'trigger_orders_del'或留空供后续人工标记) - 插入语句必须显式列出所有字段:
INSERT INTO orders_archive (id, user_id, total, archived_at, archived_by) VALUES (OLD.id, OLD.user_id, OLD.total, NOW(), 'trigger_orders_del');
大表批量删除会拖垮数据库,触发器不是万能胶
对日志表、事件表这类高频写入的大表,直接上行级 BEFORE DELETE 触发器归档,等于给每次 DELETE 加了一次同步 I/O 和锁等待。DELETE FROM logs WHERE created_at
- 归档表的
archived_at字段必须建索引,否则查“上周删了哪些订单”要全表扫 - 禁止在 >10 万行/天删除量的表上使用行级触发器归档
- 真实场景应改用定时任务:分批
INSERT INTO archive SELECT ... FROM main WHERE ... LIMIT 1000,再DELETE对应批次,控制事务大小和锁粒度
PostgreSQL 和 SQL Server 的字段兼容性陷阱最隐蔽
PostgreSQL 虽然支持 OLD.*,但一旦归档表字段顺序、类型或默认值与原表不一致,INSERT ... SELECT OLD.* 会静默截断或报错;SQL Server 的 deleted 表甚至不支持 text/ntext/image 类型,直接 SELECT * FROM deleted 会触发错误 311。
- PostgreSQL 必须显式写字段:
INSERT INTO orders_archive (id, user_id, total, archived_at) VALUES (OLD.id, OLD.user_id, OLD.total, NOW()); - SQL Server 中,含大对象字段的表,得逐字段转换:
CONVERT(VARCHAR(MAX), deleted.content),不能无脑SELECT * - 所有数据库中,归档表的主键/唯一约束不能和原表冲突——比如原表
id是主键,归档表就不能再设id为主键,否则重复删同一 ID 会失败
真正难的不是写触发器,而是想清楚:这条记录删掉后,谁会在什么时间、用什么条件去查它?归档表的查询路径、索引策略、生命周期管理,比触发器本身更消耗工程判断力。










