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