行级触发器必须加 FOR EACH ROW 才能访问 :NEW 和 :OLD;DELETE 触发器仅可用 :OLD,不可用 :NEW;UPDATE 触发器中仅 BEFORE 可修改 :NEW 字段。

必须加 FOR EACH ROW 才能访问 :NEW 和 :OLD
不写 FOR EACH ROW,触发器就是语句级的,:NEW 和 :OLD 根本不可用——编译不会报错,但运行时一用就出 PLS-00201: identifier ':NEW' must be declared 这类错误。行级触发器是唯一支持伪记录的类型,哪怕只打算记录日志,也得强制加上这句。
常见误操作包括:
- 在语句级触发器里写
:OLD.id,结果整个触发器逻辑静默失效 - 复制别人代码时漏掉
FOR EACH ROW,还以为是字段名写错了 - 用
INSTEAD OF触发器(常用于视图)却套用表级 :OLD/:NEW 习惯,实际它也要求FOR EACH ROW才能访问
DELETE 触发器里只能用 :OLD,别碰 :NEW
DELETE 操作没有新行,所以 :NEW 在 DELETE 触发器中根本不存在。试图引用 :NEW.id 会直接导致编译失败,报 PLS-00201: identifier ':NEW' must be declared。这不是运行时错误,是语法校验阶段就拦下的。
正确做法只有:
- 显式列出字段,如
:OLD.id、:OLD.name,不能写:OLD.* - 不要在 DELETE 触发器里 SELECT 原表(比如
SELECT * FROM t WHERE id = :OLD.id),会触发ORA-04091: table is mutating - 如果要记录删除时间,直接用
SYSDATE或SYSTIMESTAMP,别绕路查系统视图
UPDATE 触发器里 :OLD 和 :NEW 都可用,但修改只在 BEFORE 中生效
UPDATE 是唯一同时暴露两个伪记录的场景,但关键区别在于:只有 BEFORE UPDATE FOR EACH ROW 才允许赋值给 :NEW.column;AFTER UPDATE 里对 :NEW 赋值完全无效,也不会报错,只是默默忽略。
典型用途包括:
- 自动更新
:NEW.updated_at := SYSDATE - 校验业务规则,如
IF :NEW.salary - 防止非法变更:
IF :OLD.status = 'closed' AND :NEW.status != 'closed' THEN RAISE_APPLICATION_ERROR(...)
注意:在 WHEN 子句中写条件时,不能加冒号,比如 WHEN (OLD.status != NEW.status) —— 这里 OLD 和 NEW 不带 :,否则语法报错。
INSERT 触发器里只有 :NEW 可用,且可用来生成主键
INSERT 没有旧数据,所以 :OLD 全程不可见。这时候 :NEW 是唯一能读写的数据上下文,常用于主键自增或业务编号生成。
安全写法示例:
CREATE OR REPLACE TRIGGER tbi_emp_id BEFORE INSERT ON employees FOR EACH ROW BEGIN SELECT emp_seq.NEXTVAL INTO :NEW.id FROM dual; :NEW.created_at := SYSDATE; END;
必须避开的坑:
- 不要在
:NEW.id赋值前先SELECT COUNT(*) FROM employees——变异表风险极高 - 如果表有
NOT NULL字段,务必在触发器里给:NEW.column赋值,否则插入会因约束失败而中断 - LOB 类型字段(如
CLOB)也能通过:NEW.lob_col访问,但不能直接赋字符串超长值,得用DBMS_LOB.WRITEAPPEND等过程处理
复杂点在于:伪记录不是变量,而是只读(或仅 BEFORE 可写)的上下文快照;更隐蔽的是,哪怕语法全对,只要触发器里调了含 DML 的函数、或查了当前表,就可能在高并发下突然爆出 ORA-04091——这和 :NEW/:OLD 本身无关,但初学者常把它归咎于伪记录用错了。











