update触发器必须同时查询inserted和deleted表,通过主键join对齐行以区分新旧值;if update(col)仅判断set子句是否含该列,非值变更;after触发器不可访问text/ntext/image列。

UPDATE 触发器里必须同时查 inserted 和 deleted 表
UPDATE 操作本质是“删旧+插新”,SQL Server 会把旧值塞进 deleted 表、新值塞进 inserted 表。只查其中一个,大概率漏逻辑。
常见错误是写成 IF UPDATE(Name) BEGIN UPDATE ... SET OldName = Name FROM inserted ... END——这里直接用 inserted.Name 赋值,根本没体现“旧值”从哪来,OldName 反而存成了新值。
- 要取旧值,必须从
deleted表读,比如deleted.Name - 要取新值,必须从
inserted表读,比如inserted.Name - 两表通过主键(如
ID)做 JOIN 才能对齐行,不能假设单行更新 - 多行更新时,
@@ROWCOUNT可用来判断影响行数,避免无数据时触发器静默失败
IF UPDATE(col) 判断的是语句是否含该字段,不是值是否真变了
IF UPDATE(Type) 这种写法只看 UPDATE 语句的 SET 子句里有没有写 Type = ...,哪怕设成和原来一样的值,它也返回 true。这不是 bug,是设计如此。
如果你要“值真变了才执行”,就得手动比对:
- 先确认字段在 SET 中出现:
IF UPDATE(Type) - 再确认新旧值不同:
AND inserted.Type deleted.Type(注意 NULL 安全,必要时加IS NULL判断) - WHERE 条件里别漏掉
inserted.ID = deleted.ID,否则 JOIN 可能产生笛卡尔积
INSERT 和 DELETE 触发器中,另一张虚拟表永远为空
INSERT 触发器里 deleted 表一定没数据,DELETE 触发器里 inserted 表一定没数据。硬查它们不会报错,但查不到东西。
典型误用是写 SELECT * FROM deleted 在 INSERT 触发器里调试,结果什么也不输出,还以为触发器没跑。
- INSERT 场景:只依赖
inserted,比如记录新增日志、生成关联单据号 - DELETE 场景:只依赖
deleted,比如归档被删数据、清理外键引用 - UPDATE 场景:两张表都得用,且必须 JOIN 对齐,不能只靠
WHERE ID IN (SELECT ID FROM inserted)这种模糊匹配
AFTER 触发器不能访问 text/ntext/image 类型列
如果目标表有 text、ntext 或 image 字段,AFTER 触发器里直接 SELECT 这些列会报错:Invalid column name 'xxx' 或隐式失败。
这不是权限问题,是 SQL Server 的限制。INSTEAD OF 触发器可以绕过,但代价是你要自己重写 INSERT/UPDATE/DELETE 逻辑。
- 现代开发应优先用
varchar(max)、nvarchar(max)、varbinary(max)替代老类型 - 若无法改表结构,只能在 AFTER 触发器里避开这些列,或改用 CDC、变更追踪等替代方案
- 哪怕只是 SELECT,只要语句里写了这些列名,AFTER 触发器就会挂
实际写法里最容易被忽略的,是 UPDATE 多行时两张表的 JOIN 条件和 NULL 值比较逻辑——这两处一错,日志就写歪、同步就丢数据,还很难复现。










