Oracle中高性能DML审计触发器的关键是避开:NEW/:OLD全字段拷贝、不查表、不调用耗时函数、不写大字段、不触发递归,否则单行修改可能拖慢主事务数十毫秒。

AFTER ROW 触发器必须配 :OLD/:NEW 和 AUTONOMOUS_TRANSACTION
想捕获某行字段改前改后值,只能用 AFTER UPDATE FOR EACH ROW(或 INSERT/DELETE),BEFORE 拿不到完整 :NEW,AFTER STATEMENT 根本看不到单行数据。关键点有三个:
-
:OLD和:NEW是唯一能拿到变更前后值的引用,但只在行级触发器中有效 - 不加
PRAGMA AUTONOMOUS_TRANSACTION,审计日志会随主事务一起回滚——业务出错时,日志也丢了 - 审计表插入语句必须独立于主事务,否则主 DML 失败会导致整个操作被阻断
避免 ORA-04091 mutating table 错误的实操边界
ORA-04091 报错本质是 Oracle 禁止在行级触发器里查正在被修改的同一张表。常见踩坑行为包括:
- 写
SELECT * FROM employees WHERE emp_id = :OLD.emp_id—— 直接触发错误 - 用
JSON_OBJECT(*)或TO_CLOB(:OLD)试图一键序列化整行 —— 内部仍会隐式访问原表 - 在触发器里调用含 SELECT 的自定义函数,且该函数查了当前表
安全做法是:只显式拼接你真正关心的字段,比如 JSON_OBJECT('salary' VALUE :OLD.salary, 'emp_id' VALUE :OLD.emp_id);如需关联其他表(如部门名),确保目标表与触发器所在表无 DML 依赖链。
审计日志写入前必须做空值与变更判断
盲目记录所有操作会造成日志爆炸,尤其当字段默认值、NULL 与空字符串混用时。建议在 INSERT/UPDATE 分支里加判断:
-
IF :OLD.salary != :NEW.salary THEN ... END IF;—— 避免无意义更新刷屏 -
IF :OLD.salary IS NULL AND :NEW.salary IS NOT NULL OR :OLD.salary IS NOT NULL AND :NEW.salary IS NULL OR :OLD.salary != :NEW.salary THEN—— 正确处理 NULL - 不要直接
INSERT INTO audit_log VALUES (:OLD.*, :NEW.*)—— 字段类型不匹配或审计表结构变更会拖垮所有 DML
DDL 审计必须用系统级触发器,不能和 DML 触发器混用
想抓 CREATE TABLE、DROP INDEX 这类动作,普通表触发器完全无效。必须用数据库或 schema 级别触发器:
-
AFTER CREATE ON DATABASE监控全库 DDL -
AFTER CREATE ON SCHEMA限定到某个 schema,如IF ORA_DICT_OBJ_OWNER = 'HR' THEN ... -
ORA_LOGIN_USER和ORA_DICT_OBJ_NAME是关键上下文变量,但ORA_DICT_OBJ_SQL在部分 Oracle 版本中不可用,需提前验证 - DDL 触发器里不能
COMMIT或ROLLBACK,但可以带AUTONOMOUS_TRANSACTION写日志
最常被忽略的一点:触发器本身没有运行开关,禁用得靠 ALTER TRIGGER xxx DISABLE,而生产环境里一旦误删或权限失效,审计就彻底中断,且很难回溯缺口。


















