在UPDATE触发器中,OLD和NEW分别表示更新前旧值和更新后新值;MySQL用OLD.col NEW.col,PostgreSQL用IS NOT DISTINCT FROM实现空安全比较,需避免NULL误判与隐式类型转换。

触发器里怎么拿到更新前的旧值和更新后的新值
在标准 SQL 触发器中,OLD 和 NEW 是关键字别名,分别指向被修改行的原始状态和目标状态。但具体可用性取决于数据库类型:MySQL 和 PostgreSQL 都支持;SQL Server 用 deleted 和 inserted 临时表;SQLite 支持 OLD/NEW,但仅限于行级触发器。
关键点是:只有 UPDATE 触发器才能同时访问 OLD 和 NEW;INSERT 只能读 NEW,DELETE 只能读 OLD。
- MySQL 示例:
BEFORE UPDATE ON users FOR EACH ROW BEGIN IF OLD.status != NEW.status THEN ... END IF; - PostgreSQL 示例:
IF OLD.email IS DISTINCT FROM NEW.email THEN ... END IF;(注意用IS DISTINCT FROM处理 NULL) - 避免直接写
OLD.col = NEW.col判断相等——NULL 会导致表达式为 UNKNOWN,结果不成立
判断字段是否真的变化了,绕过 NULL 和类型隐式转换陷阱
字段值“看起来没变”,但触发器仍执行了逻辑?大概率是 NULL 比较或数据类型不一致导致的误判。比如 OLD.price = NEW.price 在任一为 NULL 时返回 false,哪怕两者都是 NULL;又比如 OLD.id 是 INT,NEW.id 是字符串 '123',某些数据库会隐式转成数字再比,但有些不会,行为不可靠。
- MySQL 推荐用
OLD.col NEW.col(空安全等于),它把两个 NULL 视为相等 - PostgreSQL 必须用
OLD.col IS NOT DISTINCT FROM NEW.col,这是标准且明确的空安全比较 - 显式类型转换更稳妥:比如
CAST(OLD.amount AS DECIMAL(10,2)) != CAST(NEW.amount AS DECIMAL(10,2)) - 对 JSON 或 TEXT 字段,注意大小写、空白符、键顺序(如 MySQL 的 JSON 字段比较可能忽略格式差异)
根据差异执行不同分支逻辑,别让触发器变成状态机
常见需求是“邮箱变了就发验证邮件,余额变了就记流水,状态从 draft→active 就生成订单”。这时候容易堆砌 if-else,但逻辑一多就难维护,还容易漏掉组合条件(比如只监控 status 变化,却没排除 status 从 active → draft 的反向操作)。
- 优先拆成多个独立触发器:一个专管 email 变更,一个专管 status 转换,职责清晰,也方便单独禁用或调试
- 状态流转类判断,写成明确的枚举对:
OLD.status = 'draft' AND NEW.status = 'active',别只写NEW.status = 'active' - 避免在触发器里调用耗时操作(如 HTTP 请求、大表 JOIN)——MySQL 的 BEFORE UPDATE 触发器里做 SELECT 是允许的,但会拖慢事务,且可能引发死锁
- 如果需要记录变更详情,用
CONCAT('status:', OLD.status, '→', NEW.status)拼接,别依赖触发器外的日志工具去还原上下文
测试触发器是否真按差异生效,而不是每次都跑
最常踩的坑是:写了条件判断,但忘了加 WHERE 子句(触发器本身没有 WHERE),或者误以为 UPDATE 语句带了 WHERE 就能限制触发器执行范围——实际上,只要该行被 UPDATE 语句命中,触发器就会执行,不管 SET 子句有没有改到对应字段。
- 测试必须覆盖三类场景:字段值不变(含全 NULL)、单字段变、多字段变。例如执行
UPDATE users SET name='Alice' WHERE id=1;,即使name原值就是'Alice',触发器也会运行 - 在触发器开头加日志写入(如 MySQL 写入
mysql.general_log或自定义日志表),记录OLD.*和NEW.*,确认是否真有差异才进分支 - PostgreSQL 中可配合
RAISE NOTICE调试,但上线前务必删掉——它会污染应用层错误捕获 - SQL Server 用户注意:
deleted/inserted是表,不是标量,需用(SELECT status FROM inserted)提取单值,且要处理多行 UPDATE 场景(触发器可能一次处理 N 行)
真正难的不是写判断,而是确保“没变就不动”——这要求你对每个字段的 NULL 行为、数据库的比较规则、触发器的执行边界都有确定性认知。少一层假设,就少一个半夜告警。

















