能,但必须建在 DATABASE 级别且用 AFTER DDL 事件;ON SCHEMA 触发器仅监控本用户 DDL,漏跨用户操作、权限变更及同义词/视图等,无法满足生产审计要求。

能,但必须建在 DATABASE 级别,且触发器需用 AFTER DDL 事件,不能只靠 ON SCHEMA —— 否则漏掉跨用户操作、权限变更、同义词/视图等关键变更。
为什么 ON SCHEMA 触发器在生产审计中不可靠
很多人照着示例写 CREATE OR REPLACE TRIGGER ddl_audit ON SCHEMA,结果上线后发现 ALTER TABLE HR.EMP 没被记录。问题出在:该触发器只捕获当前 schema(比如 SCOTT)下的 DDL,而 HR 用户执行的 DDL 不会触发 SCOTT 模式下的 schema 级触发器。
生产库多用户共存是常态,审计必须覆盖所有 owner,否则等于留了个后门。
- schema 级触发器只能监控“本用户自己发起”的 DDL,对其他用户操作完全无感
- 像
GRANT SELECT ON dept TO public、CREATE SYNONYM emp FOR scott.emp这类非表级 DDL,也不会被ON SCHEMA捕获 - Oracle 官方文档明确建议:全局审计用
ON DATABASE,不是权宜之计,而是设计前提
AFTER DDL 是唯一能覆盖全量结构变更的事件类型
DDL 触发器支持的事件很多,但只有 AFTER DDL 能统揽 CREATE / ALTER / DROP / TRUNCATE / GRANT / REVOKE / ANALYZE / COMMENT 等全部结构操作。用 AFTER CREATE OR ALTER OR DROP 列举反而容易漏——比如忘了 COMMENT ON COLUMN 也算 DDL 变更。
注意:BEFORE DDL 无法用于审计日志(因事务未提交,日志写入可能回滚),也禁止在其中执行任何 DDL(如 EXECUTE IMMEDIATE),否则直接报 ORA-14552。
-
AFTER DDL在语句成功执行后触发,确保变更已落地,日志可持久化 - 它自动包含所有标准 DDL 类型,无需手动枚举,避免遗漏新型语法(如 Oracle 23c 的
ALTER TABLE ... VALIDATE CONSTRAINT) - 不支持
FOR EACH ROW,它是语句级触发器,天然适配 DDL 场景
必须用 EVENTDATA() 解析真实 SQL,别信 ORA_DICT_OBJ_SQL
ORA_DICT_OBJ_SQL 是个陷阱:它只在极少数 DDL(如 CREATE TABLE)中返回非空值,大部分时候为 NULL。真正稳定获取原始语句的方式是 EVENTDATA() —— 它返回 XML,里面含完整 T-SQL 或 PL/SQL 文本。
常见错误是直接拼接 ORA_DICT_OBJ_OWNER || '.' || ORA_DICT_OBJ_NAME 当作对象名,但像 CREATE INDEX idx_on_dept ON dept(loc) 这种语句,ORA_DICT_OBJ_NAME 是 IDX_ON_DEPT,而实际变更主体是 DEPT 表,漏掉上下文就失去审计意义。
- 用
EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR2(4000)')提取原始语句(注意:Oracle 中该函数返回 CLOB,需转 VARCHAR2 截断) - 用
EVENTDATA().value('(/EVENT_INSTANCE/ObjectType)[1]', 'VARCHAR2(30)')和OBJECTNAME获取被操作对象类型与名称,比依赖绑定变量更可靠 - 避免在触发器里调
DBMS_UTILITY.FORMAT_CALL_STACK或查V$SESSION——这些在ON DATABASE上下文中权限常不足,易抛 ORA-00604
日志表字段设计要防时区漂移和权限断裂
生产环境最常踩的坑不是逻辑错,而是字段设计没扛住多租户+跨时区+权限隔离三重压力。比如用 CURRENT_DATE 记时间,DBA 在不同时区会话里执行 DDL,日志时间就乱序;又比如日志表建在 APP 用户下,但触发器由 SYS 创建并运行在 SYSTEM 上下文,INSERT 就因缺失 INSERT 权限直接失败。
- 时间字段必须用
SYSTIMESTAMP(带时区)或SYSDATE(UTC),禁用CURRENT_DATE、CURRENT_TIMESTAMP - 日志表必须建在 DBA 可控 schema(如
AUDIT_ADMIN),且显式授予INSERT给PUBLIC或至少EXECUTE_CATALOG_ROLE - 加
CLIENT_IDENTIFIER和OS_USER字段(通过SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')和SYS_CONTEXT('USERENV', 'OS_USER')),否则无法区分是应用连接池还是 DBA 手动操作
复杂点在于:EVENTDATA() 返回的 XML 结构随 Oracle 版本微调,比如 19c 和 23c 的 /EVENT_INSTANCE/PostTime 路径可能不同,上线前务必在目标版本做实测解析。别指望一次写完永久有效。


















