sql server中after update触发器通过deleted表获取旧值、inserted表获取新值,必须用主键或唯一约束列inner join关联两表,禁止按顺序匹配或null直接比较。

SQL Server UPDATE触发器里怎么拿到旧值和新值
在 AFTER UPDATE 触发器中,DELETED 表存更新前的整行,INSERTED 表存更新后的整行——这是 SQL Server 的底层语义:UPDATE 被实现为“先删旧行、再插新行”。两张表结构完全一致,且行数严格相等,一一对应。
你不能用 OLD.col 或 NEW.col(那是 MySQL/PostgreSQL 的写法),也不能靠 SELECT TOP 1 FROM DELETED 猜顺序——这些在 SQL Server 里要么语法报错,要么逻辑不可靠。
-
DELETED和INSERTED是只读内存表,无法CREATE/DROP,但可在触发器体内直接SELECT或JOIN - 如果原表有 50 列,这两张表每行都带全量数据;一次更新 10 万行,触发器里无谓的
SELECT *可能直接拖垮性能 - 别在触发器里写
IF EXISTS(SELECT 1 FROM INSERTED) AND NOT EXISTS(SELECT 1 FROM DELETED)来“判断是不是 INSERT”——AFTER UPDATE中DELETED必然非空,这种判断纯属冗余
如何安全关联 DELETED 和 INSERTED 表取字段差异
必须用主键(或唯一约束列)做 INNER JOIN,否则无法确定哪条旧记录对应哪条新记录。没有主键的表,DELETED 和 INSERTED 的行序不保证一致,按顺序匹配是典型错误。
- 单列主键:写
ON d.id = i.id,不要漏别名(d和i是常用简写) - 复合主键:比如
(order_id, line_no),就得写全ON d.order_id = i.order_id AND d.line_no = i.line_no - 避免
LEFT JOIN或RIGHT JOIN——除非你在INSTEAD OF触发器中要区分 INSERT/UPDATE/DELETE 场景 - 绝对不要用
ORDER BY (SELECT NULL)或ROW_NUMBER()强行对齐——两表无隐式顺序,这种技巧在并发或批量更新下必然出错
常见错误现象与实际场景对照
以下错误不是语法问题,而是语义误用,上线后难排查:
- 触发器逻辑正常跑通,但批量更新时日志里出现“旧金额=新金额”或字段错配——大概率是 JOIN 条件漏了复合主键中的某一列
- 触发器执行缓慢,
SET STATISTICS IO ON显示扫描了大量逻辑页——检查是否写了SELECT * FROM DELETED d INNER JOIN INSERTED i ON ...,应只选需要的列,如SELECT d.id, d.status, i.status - 触发器里插入审计日志,但发现部分更新没记录——可能用了
IF d.status != i.status却没处理NULL比较(NULL != 'A'结果是UNKNOWN),得改用ISNULL(d.status, '') != ISNULL(i.status, '') - 在视图上建了
INSTEAD OF UPDATE触发器,却直接把DELETED当原表查——注意:此时DELETED和INSERTED仍可用,但是否真发生了变更,得由你代码判断,系统不保证
性能与兼容性容易被忽略的点
很多人以为 DELETED / INSERTED 是轻量级“变量”,其实它们是完整行副本的内存表。宽表 + 高频更新时,这里就是瓶颈源头。
- 如果原表含
text/ntext/image列(已弃用但仍存在老系统),AFTER触发器里不能直接查这些列,会报错;INSTEAD OF触发器才允许访问 - 触发器影响多行时(比如
UPDATE orders SET status='shipped' WHERE created_date ),<code>@@ROWCOUNT必须在触发器开头立刻捕获,后续任何SELECT都可能覆盖它 - 不要在触发器里调用耗时存储过程或远程查询——事务会卡住整个 UPDATE,下游应用可能超时
- SQL Server 2019+ 支持
STRING_AGG在触发器里拼差异字段,但要注意聚合前先过滤掉未变化的行,否则日志膨胀极快
最常被跳过的一步是:确认目标表是否有主键或唯一约束。没有它,DELETED 和 INSERTED 的关联就失去依据,所有基于“前后对比”的逻辑都会漂移。别依赖文档说“应该有”,去查 sys.key_constraints 或 SSMS 的索引节点,亲眼看到才作数。










