sql server 2019 归档必须用 after delete 触发器,确保主表数据已物理删除且 deleted 表完整可用;须显式列字段插入、归档表结构与源表严格对齐、禁查原表或耗时操作。

SQL Server 2019 必须用 AFTER DELETE 触发器
AFTER DELETE 是唯一能稳定捕获 DELETED 表数据的时机。BEFORE 不支持,INSTEAD OF 会阻止原删除动作执行——你删了但数据没真走,归档表也空着,最后发现两边都丢了数据。
常见错误是看到“before”字面意思就选 INSTEAD OF,结果日志里查不到 DELETE 语句成功记录,应用层却反馈“删除成功”,实际是触发器把操作吞掉了。
-
AFTER DELETE确保原行已从主表物理移除,DELETED表内容完整、只读、可安全读取 - 别写
INSTEAD OF DELETE+ 手动DELETE,这多一层事务嵌套,且容易漏掉SET NOCOUNT ON导致应用报错 - 显式加上
FOR EACH ROW(虽然 SQL Server 默认就是行级,但写上更防误读)
INSERT INTO archive_table SELECT ... FROM DELETED 的写法陷阱
不能偷懒写 SELECT * FROM DELETED,也不能用单变量赋值(如 SELECT @id = id FROM DELETED),否则批量删多行时直接崩。
SQL Server 报错典型有:Subquery returned more than 1 value(变量赋值)、Column name or number of supplied values does not match table definition(字段数/顺序不匹配)。
- 归档表字段必须与源表**完全对齐**:名称、类型、NULL 性、长度(比如源表
TEXT,归档表不能是TINYTEXT) - 自增列(
IDENTITY)在归档表中必须改为普通INT NOT NULL,否则插入失败 - 必须显式列出字段:
INSERT INTO orders_archive (id, customer_id, total, created_at) SELECT id, customer_id, total, created_at FROM DELETED - 额外字段(如
op_type、archived_at)要写在SELECT列表里,不能靠归档表默认值——DEFAULT GETDATE()在触发器里不可靠
归档表结构初始化建议
别手动一个字段一个字段敲建表语句。错一个类型或少一个 NULL,触发器一跑就挂。
最稳做法是从源表 DDL 派生:
- 用
SELECT * INTO orders_archive FROM orders WHERE 1=0快速建结构(含类型、NULL 性) - 立刻删掉归档表的
PRIMARY KEY、IDENTITY、DEFAULT、CHECK约束(这些在归档场景全是累赘) - 手动加两列:
op_type VARCHAR(10) NOT NULL、archived_at DATETIME2 NOT NULL - 给
archived_at加索引:CREATE INDEX IX_orders_archive_archived_at ON orders_archive(archived_at),否则查历史快照极慢
触发器里禁止做的事
看似合理,实则高危。很多线上事故就出在这几条上。
- 禁止在触发器里再查原表(如
IF EXISTS(SELECT 1 FROM orders WHERE id IN (SELECT id FROM DELETED))),会引发死锁或阻塞 - 禁止调用远程服务、HTTP 请求、文件写入等耗时操作,
DELETE会卡住甚至超时回滚 - 禁止对同一库其他表做
INSERT/UPDATE/DELETE以外的 DDL 操作(如ALTER TABLE),权限和事务上下文可能不满足 - 禁止用
GETUTCDATE()或SYSDATETIMEOFFSET()替代GETDATE(),除非归档表字段明确声明为DATETIMEOFFSET
归档逻辑越薄越好。真正复杂的清洗、脱敏、分发,应该由外部任务(如 SQL Agent Job + SSIS 或 .NET 后台服务)定时拉取 archived_at 范围内的数据来处理——触发器只管“存下来”,别管“怎么用”。










