只有用AFTER UPDATE OF column_name + FOR EACH ROW + WHEN子句,配合:NEW/:OLD引用,才能精准响应列值真实变化;UPDATE OF仅检测SQL是否显式列出该列,不判断值是否真变,需WHEN过滤且妥善处理NULL。

直接回答:用 AFTER UPDATE OF column_name + FOR EACH ROW + WHEN 子句,再配合 :NEW 和 :OLD 引用,才能真正实现“只响应某列变更”的精细化控制。光靠 UPDATE OF 不够,漏掉 WHEN 或误用 :OLD/:NEW 会导致逻辑错判。
为什么 UPDATE OF d 不能单独判断列值是否真变了
Oracle 的 UPDATE OF d 只检查 SQL 语句里是否显式列出了 d 字段,不管它实际值变没变。比如执行 UPDATE test001 SET d = d WHERE a = '1',虽然 d 值没变,但触发器仍会触发。
- 必须加
WHEN (:OLD.d != :NEW.d)才能过滤出真实变更 -
WHEN子句只对行级触发器有效,且不能引用函数(如NVL)或子查询 - 注意 NULL 比较:
:OLD.d != :NEW.d在任一为 NULL 时结果为 UNKNOWN,需显式写成(:OLD.d != :NEW.d OR :OLD.d IS NULL != :NEW.d IS NULL)
:NEW 和 :OLD 在列变更触发中的典型误用
常见错误是把 :NEW.d 当成“当前表最新值”去查其他表,或者在 UPDATE 语句里直接写 test001.d —— 这会报 ORA-04091(表正在变异)。
- 行级触发器中禁止对触发器所在表做 DML,
UPDATE test002是安全的,但UPDATE test001会失败 - 所有跨表操作必须基于
:NEW和:OLD提供的快照值,例如同步test002.d应该用:NEW.d,而不是再去查test001 - 如果要根据
:NEW.a更新test002,WHERE 条件必须用t2.a = :NEW.a,不能写成子查询关联原表
完整可运行的精细化列变更触发器写法
以下示例严格响应 test001.d 列的真实变更,并同步更新 test002.d:
CREATE OR REPLACE TRIGGER test002_sync_on_d_change
AFTER UPDATE OF d ON test001
FOR EACH ROW
WHEN (NVL(:OLD.d, '§') != NVL(:NEW.d, '§'))
BEGIN
UPDATE test002 t2
SET t2.d = :NEW.d
WHERE t2.a = :NEW.a;
END;
说明:
-
NVL(:OLD.d, '§')是处理 NULL 的常用技巧,避免三值逻辑陷阱 - 没用
EXISTS子查询,因为:NEW.a已知,直接 WHERE 更高效 - 触发时机选
AFTER,确保:NEW.d已稳定;若需校验后拦截,才用BEFORE
最易被忽略的一点:触发器本身不保证事务原子性——如果 UPDATE test002 失败(比如违反约束),整个事务会回滚,但很多人以为只是触发器失败。务必在测试时故意让 test002 出错,确认主表更新也被撤销。











