AFTER触发器必须用集合操作处理INSERTED/DELETED表,因其始终为表结构而非单行变量;UPDATE()仅检测字段是否出现在SET子句中,不反映实际值变化,真实变更需对比inserted与deleted表并妥善处理NULL。

AFTER 触发器必须显式处理多行操作
SQL Server 的 INSERTED 和 DELETED 表永远是表结构,哪怕只改了一行数据,它们也至少含一行——但绝不是“单行变量”。很多初学者写触发器时直接用 SELECT @var = column FROM inserted,结果在批量 UPDATE 时出错或只取到任意一行,逻辑彻底失效。
- 永远用集合操作:比如用
JOIN inserted更新关联表,而不是SELECT TOP 1取值再循环 - 避免在触发器里写游标或
WHILE循环,性能会断崖式下降 - 如果真要逐行判断(极少数场景),用
EXISTS或窗口函数配合ROW_NUMBER(),别依赖顺序
INSTEAD OF 触发器不能用于有级联外键的表
当你对一个设置了 ON DELETE CASCADE 的子表创建 INSTEAD OF DELETE 触发器时,SQL Server 会直接报错:Msg 3724, Level 16, State 2: Cannot create INSTEAD OF DELETE or UPDATE trigger on table 'xxx' because the table has a foreign key with cascading delete or update.
- 这是硬性限制,不是配置问题;DDL 层面就禁止了
- 若需拦截并重写删除逻辑,得先去掉级联,改用
AFTER DELETE+ 手动清理子表 -
INSTEAD OF更适合视图,尤其是多表联合视图的更新场景
UPDATE() 函数只能检测字段是否出现在 SET 子句中,不反映实际值变化
IF UPDATE(Price) 在 UPDATE products SET Price = Price 这种“伪更新”里也会返回 true——它只看语法上有没有写这个字段,不比较新旧值。这会导致审计日志误记、库存重复扣减等严重问题。
- 真正判断值是否变化,必须对比
inserted.Price和deleted.Price - 注意 NULL 处理:用
ISNULL(i.Price, -1) != ISNULL(d.Price, -1)或EXCEPT更安全 -
UPDATE()仅适合轻量级前置过滤,比如“只要改了 Status 就走审批流”,不用于业务逻辑分支
触发器里不要调用远程服务器或外部 API
触发器运行在事务上下文中,一旦调用 OPENQUERY、sp_OACreate 或链接服务器,就会把整个事务拖入分布式事务协调(MSDTC),极易超时、死锁,且无法回滚外部副作用。
- 审计类需求改用 Service Broker 异步投递,或写入本地日志表由外部服务轮询
- 需要强一致性同步的,优先考虑 CDC(变更数据捕获)或应用层双写
- 哪怕只是发邮件,也应写入队列表,由 SQL Agent Job 定期处理
最常被忽略的一点:触发器里的 PRINT 或 RAISERROR 不会返回给客户端,而是进 SQL Server 错误日志——除非你用 WITH LOG 且有管理员权限,否则调用方根本不知道触发器失败了。

















