if update() 判断的是sql语句中是否显式包含某列,而非值是否实际变化;检测真实变更须用inserted/deleted表比对,推荐is distinct from或null安全的三条件判断。

IF UPDATE() 不是用来判断“值变了没”,而是判断“SQL语句里有没有写这个列”——用错就全错了。
IF UPDATE() 只响应 SET 子句中显式出现的列
它不看数据,只看语法。哪怕你执行 UPDATE Users SET Email = Email,UPDATE(Email) 仍返回 1;但如果你漏写了 Email,哪怕其他字段更新导致该列实际值变化(比如触发了计算列或级联),UPDATE(Email) 也返回 0。
常见错误现象:日志表疯狂记录“Email 更新”,查数据发现前后都是 alice@example.com——这就是把语法层面的“被 SET”当成了语义层面的“值变更”。
-
UPDATE()必须用在IF或WHERE中,不能单独执行 - 参数必须是真实列名,如
UPDATE(Phone),不能是变量或表达式 - 对
NULL赋值也算“被更新”,SET Phone = NULL→UPDATE(Phone)返回1 - 对计算列、
IDENTITY列、ROWVERSION列始终返回0,哪怕它们被间接影响
真正检测“值是否变化”必须手动比对 inserted/deleted
要确认字段值真变了,唯一可靠方式是在触发器里显式比较 inserted 和 deleted 表对应行的值。尤其要注意 NULL 场景:old_value = new_value 在任一为 NULL 时结果为 UNKNOWN,不能直接用 = 或 !=。
推荐写法:
IF EXISTS (
SELECT 1
FROM inserted i
INNER JOIN deleted d ON i.ID = d.ID
WHERE i.Email IS DISTINCT FROM d.Email
)
说明:
-
IS DISTINCT FROM是 SQL Server 2022+ 支持的健壮比较方式,自动处理NULL对比 - 老版本可用
(i.Email != d.Email) OR (i.Email IS NULL AND d.Email IS NOT NULL) OR (i.Email IS NOT NULL AND d.Email IS NULL) - 别写成
IF i.Email != d.Email——单行触发器里没有上下文,必须用EXISTS + JOIN关联两表
COLUMNS_UPDATED() 更适合多字段组合判断和防循环
当你要响应“Name 或 Email 或 Status 中任意一个被改”这类逻辑,或者想避免触发器自己引发的二次更新循环,COLUMNS_UPDATED() 比堆砌多个 UPDATE() 更可靠。
它返回一个位图 varbinary,每个 bit 对应表中一列(从左到右,第 0 位是第一列)。例如表 Users(ID, Name, Email, Status),执行 UPDATE Users SET Name = 'A', Status = 'OK' 后,COLUMNS_UPDATED() 返回二进制 1010(十进制 10)。
- 判断第 N 列(索引从 0 开始)是否被 SET:
IF (COLUMNS_UPDATED() & POWER(2, N)) = POWER(2, N) - 判断 Name(假设是第 1 列)或 Email(第 2 列)被改:
IF (COLUMNS_UPDATED() & 0x06) > 0(0x06 = 0b0110) - 它不依赖列名拼写,不受大小写/排序规则干扰,也绕过计算列误判问题
触发器里别做重操作,否则批量更新会卡死
每次 UPDATE 一行就触发一次,如果里面嵌套 SELECT 查询、调用函数或更新其他表,100 行更新就会执行 100 次相同逻辑——性能断崖式下跌。
实操建议:
- 只做轻量判断和赋值,比如
UPDATE t SET updated_at = GETDATE(), version = version + 1 - 需要查关联表?挪到应用层,或用异步消息(如 Service Broker)解耦
- 别在触发器里用
EXECUTE动态 SQL,也别调用可能阻塞的存储过程 - 跨表更新防循环的关键不是
UPDATE(),而是业务层控制流向,或加标记字段跳过递归
最常被忽略的一点:即使你用 UPDATE(col) 做了条件判断,只要触发器内又去更新另一张带触发器的表,循环就成立——UPDATE() 在那张表的触发器里照样返回 1,因为它只认“这次语句有没有 SET 那列”,不管谁发起的。










