Oracle高性能DML审计触发器的关键是避开:NEW/:OLD全字段拷贝、不查表、不调用耗时函数、不写大字段、不触发递归;否则单行修改可能拖慢主事务数十毫秒。
直接说结论:oracle中高性能dml审计触发器的关键不是“写得漂亮”,而是**避开 :new 和 :old 的全字段拷贝、不查表、不调用耗时函数、不写大字段、不触发递归**——否则哪怕只改一行,也可能拖慢主事务几十毫秒。
为什么 AFTER EACH ROW 触发器在高并发下容易变慢
行级触发器每影响一行就执行一次PL/SQL逻辑,而审计场景里常犯的错误是:把整条记录 INSERT INTO audit_log VALUES (:NEW.*) 直接插入,尤其当原表有 CLOB、BLOB 或长 VARCHAR2 字段时,实际复制的是数据副本而非引用;更糟的是,有人在触发器里调用 SYS_CONTEXT('USERENV', 'IP_ADDRESS')——这个函数在11g/12c中可能触发网络栈查询,单次耗时可达5–20ms。
- 避免在触发器中读取
CLOB/BLOB字段(哪怕只是:NEW.my_clob引用也会触发隐式读取) - 用
USER代替SYS_CONTEXT('USERENV', 'SESSION_USER'),前者是常量,后者需上下文解析 - 不要在触发器里执行
SELECT ... FROM some_table—— 即使是查配置表,也极易引发锁或一致性读开销 -
AFTER触发比BEFORE多一次数据页访问,审计日志建议统一用BEFORE(只要不依赖已提交值)
INSERT/UPDATE/DELETE 分支必须严格隔离
Oracle触发器里 IF INSERTING THEN ... ELSIF UPDATING THEN ... ELSIF DELETING THEN 看似自然,但注意:UPDATING('COL') 是安全的,而 UPDATING(无参数)在复合DML中可能误判——比如 UPDATE ... SET x=1 WHERE y=2 但没匹配到行,此时 UPDATING 仍为 TRUE,但 :OLD 全为空,容易导致 INSERT INTO audit ... VALUES (:OLD.col) 插入一堆 NULL 而不报错,日志失真。
- 始终用
IF INSERTING THEN/ELSIF UPDATING THEN/ELSIF DELETING THEN三层硬分支 - 更新操作要显式判断字段是否真被修改:
IF UPDATING AND :OLD.name != :NEW.name,避免无意义日志 - 删除分支中
:NEW全为NULL,只能用:OLD;插入分支中:OLD全为NULL,只能用:NEW - 别用
WHEN (condition)做过滤——它只在语句级生效,对行级触发器无效
审计表设计直接影响触发器吞吐量
审计表不是越全越好。字段多、类型重、索引多,会放大每次 INSERT 的redo生成量和buffer busy waits。实测发现:审计表含3个 VARCHAR2(4000) 字段时,单次DML触发日志写入延迟比含3个 VARCHAR2(100) 高4–6倍。
- 审计表主键用
SEQUENCE+NOORDER,别用IDENTITY(12c+虽支持,但高并发下序列争用明显) - 时间字段用
SYSTIMESTAMP而非SYSDATE,后者精度仅秒级,同秒内多操作会撞时间戳 - 客户端信息只存关键项:
USER、SYS_CONTEXT('USERENV','HOST')、USERENV('SID')——IP_ADDRESS在RAC或代理环境下不可靠且慢 - 原表主键字段单独建列(如
deptno NUMBER),别塞进一个大CLOB字段里,否则无法高效分区或清理
真正该禁用的几类操作
很多团队给所有表加审计触发器,结果发现JOB批量更新时系统IO飙升。这不是触发器写得不好,而是不该让它运行。
- 禁止对临时表(
GLOBAL TEMPORARY TABLE)建DML审计触发器——内容本就不持久,日志无意义 - 禁止在物化视图日志表(如
MLOG$_xxx)上建触发器——Oracle内部维护机制可能冲突 - 应用明确用
/*+ APPEND */直接路径插入时,行级触发器根本不会触发,但开发者常误以为“已审计” - DBA执行
ALTER TABLE ... MOVE或SHRINK SPACE会绕过触发器,这类DDL变更需另配DDL触发器捕获
最易被忽略的一点:触发器本身没有事务上下文继承,但它的 INSERT 会参与主DML事务。如果审计表空间不足或触发器里抛异常(比如违反约束),整个业务DML会回滚——这比慢更危险。上线前务必用真实负载压测,而不是只验证单条SQL。



















