必须使用 BEFORE INSERT OR UPDATE FOR EACH ROW 触发器配合 RAISE_APPLICATION_ERROR 才能阻止非法数据,AFTER 触发器无法回滚已执行的 DML,语句级触发器无法访问 :NEW/:OLD 值,且需规避变异表错误。

FOR EACH ROW 触发器在 PL/SQL 中不能直接用于校验并阻止非法数据插入或更新,除非配合 SIGNAL 或异常抛出——但 Oracle PL/SQL 不支持 SIGNAL(那是 MySQL 的语法),必须用 RAISE_APPLICATION_ERROR 才能中止事务。
BEFORE INSERT OR UPDATE 里用 RAISE_APPLICATION_ERROR 拦截非法值
Oracle 的行级触发器无法像 MySQL 那样靠 SIGNAL SQLSTATE 抛错;它依赖 PL/SQL 异常机制。只要在 BEFORE 触发器中检测到不合规数据,就主动抛出带错误码的异常,数据库会自动回滚当前 DML 操作。
-
RAISE_APPLICATION_ERROR(-20001, 'Salary must be >= 3000')是标准写法,错误码必须是 -20000 到 -20999 之间 - 必须放在
BEFORE INSERT OR UPDATE中,AFTER触发器无法阻止操作执行 - 不要在触发器里写
COMMIT或ROLLBACK—— Oracle 明确禁止,会报ORA-04092 - 若校验逻辑复杂(比如查其他表),注意避免“变异表”错误(
ORA-04091):不能在行级触发器中直接SELECT正被修改的同一张表
CREATE OR REPLACE TRIGGER check_emp_salary BEFORE INSERT OR UPDATE ON employees FOR EACH ROW BEGIN IF :NEW.salary <h3>为什么不能用 AFTER 触发器做有效性校验</h3><p><code>AFTER</code> 触发器在 DML 已完成之后才运行,此时数据已写入表、约束已通过、事务可能已部分提交(尤其在自治事务下更危险)。即使你在里面抛错,原始 INSERT/UPDATE 不会被回滚——你只中断了触发器自身逻辑,用户收到错误但脏数据已落库。</p>
- 想“补救”只能手动
DELETE或UPDATE,但并发下极易出错 - 审计类、日志类、通知类逻辑适合
AFTER,但校验类逻辑必须用BEFORE - Oracle 不允许在
AFTER行级触发器中修改 :NEW 或 :OLD 值(它们只读)
区分行级与语句级触发器对校验的影响
没加 FOR EACH ROW 就是语句级触发器,它只执行一次,且无法访问单行的 :NEW 或 :OLD 值——这意味着你根本拿不到待插入那条记录的 salary 字段,校验无从谈起。
- 必须显式写
FOR EACH ROW,否则触发器体里出现:NEW.salary会报PLS-00049错误 - 语句级触发器适合统计类操作(如“本次共插入 5 条记录”,需配合
SQL%ROWCOUNT) - 行级触发器性能开销随影响行数线性增长,高频写入表慎用
真正容易被忽略的是变异表问题:当你在 employees 的行级触发器里试图 SELECT COUNT(*) FROM employees WHERE dept_id = :NEW.dept_id,Oracle 会直接报 ORA-04091。绕过方式只有三种:改用语句级触发器 + 临时表、用自治事务(PRAGMA AUTONOMOUS_TRANSACTION,但会脱离原事务上下文)、或把校验逻辑移到应用层或 CHECK 约束中。










