在FOR EACH ROW触发器中用:OLD和:NEW获取修改前后字段值,需加冒号、带表名或点号引用;:OLD仅用于UPDATE/DELETE,:NEW仅用于INSERT/UPDATE;BEFORE行触发器可赋值:NEW,AFTER中赋值报错;禁止在行级触发器中查询正在修改的表以避免ORA-04091。
触发器里怎么拿到修改前后的字段值
用 :old 和 :new 就行,但只在 for each row 触发器里有效。它们不是变量,是伪记录(pseudo-record),访问字段必须带表名前缀或直接用点号,比如 :new.id、:old.status。
常见错误是写成 NEW.id(漏掉冒号)或在 FOR EACH STATEMENT 里硬用——这时会报错 ORA-04082: NEW or OLD references not allowed in table level triggers。
-
:OLD在UPDATE和DELETE中可用;:NEW在INSERT和UPDATE中可用 - 对
INSERT,:OLD所有字段为NULL;对DELETE,:NEW全为NULL - 不能在触发器里给
:NEW字段赋值后,又用SELECT ... INTO :NEW.col这种写法——Oracle 不允许对伪记录做INTO
为什么 UPDATE 触发器里改了 :NEW.col,查出来的却是旧值
因为 :NEW.col 只影响当前 DML 语句最终写入的值,不改变触发器执行时已读取的行数据。比如你在 BEFORE UPDATE 里设 :NEW.price := :NEW.price * 1.1,后续触发器逻辑里再查 :NEW.price 是新值,但查表里原行还是旧值。
容易踩的坑是误以为改了 :NEW 就能“提前看到”更新结果,其实它只是个写入缓冲区。真正生效要等整个语句提交。
- 只在
BEFORE ROW触发器中允许赋值给:NEW字段;AFTER里赋值会报ORA-04084: cannot change NEW values for this trigger type - 如果触发器里做了
SELECT ... FROM my_table WHERE id = :NEW.id,查到的是更新前的数据(除非加FOR UPDATE或用闪回查询) - 并发场景下,别依赖
:NEW值去查其他行并做业务判断——可能被其他事务覆盖
FOR EACH ROW 触发器性能差,什么时候该避开
每行都进一次触发器体,如果有批量 INSERT /*+ APPEND */ 或 MERGE 上万行,开销会明显高于语句级触发器。尤其当触发器里含 SELECT、INSERT 或调用 PL/SQL 函数时,行数一多就卡。
不是所有场景都适合换,得看目的:如果只是为了审计日志,可以改用物化视图日志 + 定时同步;如果必须实时校验字段逻辑,又涉及多表关联,那得接受这个代价,或把校验逻辑提到应用层。
- 避免在
FOR EACH ROW里写COMMIT或ROLLBACK——会报ORA-04092: cannot commit in a trigger - 用
AUTONOMOUS_TRANSACTION包裹日志插入能解耦,但要注意它看不见主事务未提交的数据 - Oracle 12c 起支持
WITH FUNCTION写在 SQL 里做轻量计算,有时比触发器更可控
触发器里引用其他表字段,为什么总报 ORA-04091
这是“变异表”错误(mutating table error),本质是 Oracle 禁止在行级触发器里直接 SELECT 正在被修改的表。比如在 emp 的 BEFORE UPDATE FOR EACH ROW 里写 SELECT COUNT(*) FROM emp WHERE dept_id = :NEW.dept_id,就会崩。
根本原因是 Oracle 锁和一致性读机制不允许在变更过程中重新读本表——它不知道该读哪个 SCN 的快照。
- 最稳解法:用复合触发器(
COMPOUND TRIGGER),把查询逻辑挪到AFTER STATEMENT段,用集合变量暂存:NEW数据 - 次选:用
AUTONOMOUS_TRANSACTION开独立事务查,但注意查不到主事务未提交的变更 - 别试
PRAGMA AUTONOMOUS_TRANSACTION加SELECT FOR UPDATE——这只会让死锁概率飙升
事情说清了就结束

















