mysql、sql server、postgresql触发器中判断字段变更需分别用空安全比较(old.col new.col)、主键join关联deleted/inserted表、is not distinct from;审计应行级存储、大字段哈希、显式授权。

MySQL触发器里怎么安全判断OLD和NEW字段是否真变了
直接用 OLD.col != NEW.col 会漏掉 NULL 场景:当两者都是 NULL 时,表达式结果为 UNKNOWN,IF 条件不成立。这不是“没变”,而是比较失效。
- 必须改用空安全等于:
OLD.col NEW.col—— 它把两个NULL视为相等,返回1;值不同返回0 - 只对关键业务字段做判断,比如
status、email、amount,别写OLD.* != NEW.*这种全字段 OR 判断 - 字符串区分大小写?默认校对规则下
'A' = 'a'可能为true,需要严格比时加BINARY:BINARY OLD.name != BINARY NEW.name - 避免在判断逻辑里调子查询或函数(如
SELECT COUNT(*)),否则触发器延迟飙升,还可能引发锁等待
SQL Server触发器中如何正确关联DELETED和INSERTED表
SQL Server 没有 OLD/NEW,靠 DELETED 和 INSERTED 两张伪表。但它们**没有隐式顺序**,不能按行号或 SELECT TOP 1 猜对应关系——这是线上事故高发点。
- 必须用主键或唯一约束列做
INNER JOIN,例如单主键:FROM DELETED d INNER JOIN INSERTED i ON d.id = i.id - 复合主键必须写全,漏一列就会一对多爆炸:
ON d.order_id = i.order_id AND d.line_no = i.line_no - 禁止用
ORDER BY (SELECT NULL)或ROW_NUMBER()强行对齐——并发批量更新时必然错配 - 查差异时只选必要字段,别写
SELECT * FROM DELETED d INNER JOIN INSERTED i ON ...,大表扫全量列会拖垮性能
PostgreSQL触发器中NULL比较的唯一可靠写法
PostgreSQL 不支持 ,也不能用 = 直接比含 NULL 的字段。写 OLD.email = NEW.email 在任一端为 NULL 时返回 NULL,导致条件失效。
- 唯一标准且明确的方式是:
OLD.email IS NOT DISTINCT FROM NEW.email—— 它把两个NULL视为相等 - 反向判断“是否变化”就写:
OLD.email IS DISTINCT FROM NEW.email - JSON 字段别直接
=,先标准化:jsonb_strip_nulls(OLD.config) = jsonb_strip_nulls(NEW.config) - 时间字段注意精度:
updated_at可能带毫秒,比之前先date_trunc('second', OLD.updated_at)
触发器里记录字段级差异最容易被忽略的三件事
很多人以为把 OLD.col 和 NEW.col 插进日志表就完事了,但上线后常发现:该记的没记、不该记的记了一堆、查起来慢得没法用。
- 审计表结构别用宽表存所有字段,推荐“一行一变更”:每条记录只存一个字段的变化,含
table_name、row_id、column_name、old_value、new_value - 大字段(
TEXT、JSONB)别全量存,超 1000 字符存sha256(value),否则日志表几天就膨胀到 TB 级 - 触发器权限必须显式授予:
GRANT INSERT ON audit_log TO trigger_owner,否则整个UPDATE事务会因权限不足直接失败
真正难的不是写出能跑的触发器,而是让每次批量更新时,它既不丢记录、也不拖慢主业务、更不会在 NULL 或类型转换上悄悄出错。这些细节不在语法手册里,但在生产环境里天天咬人。











