能实现全量审计,但必须用after ddl on schema触发器且日志表与触发器同schema;database级触发器因权限高、性能差、ora_dict_obj_owner不可靠而不可取。

能实现,但必须用 AFTER DDL ON SCHEMA 触发器,且日志表和触发器必须在目标 Schema 下创建——跨 Schema 写日志或在 SYS 下建触发器都会因权限或上下文错乱失败。
为什么不能用 DATABASE 级触发器做“全量审计”
Database 级触发器确实能捕获所有用户的 DDL,但代价太高:它要求 ADMINISTER DATABASE TRIGGER 权限,且每次触发都在数据库级事务中打开匿名事务,容易拖慢全局 DDL 响应;更关键的是,ORA_DICT_OBJ_OWNER 在 database 级触发器中可能为空或不可靠,导致无法准确归因操作者。实际生产中,绝大多数“全量审计”需求其实只要覆盖几个核心业务 Schema(如 SCOTT、HR),而非真管整个库。
AFTER CREATE OR DROP OR ALTER ON SCHEMA 的写法要点
一个健壮的 Schema 级 DDL 审计触发器要同时覆盖常见变更类型,不能只写 AFTER CREATE——否则 DROP TABLE 和 ALTER TABLE ADD COLUMN 就漏掉了。
- 事件列表必须显式列出:
AFTER CREATE OR DROP OR ALTER OR RENAME OR TRUNCATE OR GRANT OR REVOKE,不能简写为DDL(Oracle 不支持该通配符) - 必须用
ORA_DICT_OBJ_TYPE和ORA_DICT_OBJ_NAME获取对象信息,而不是查USER_OBJECTS——后者在触发时可能已失效(比如DROP过程中表已不存在) - SQL 文本要用
ORA_DICT_OBJ_SQL,但它在GRANT/REVOKE场景下为空,需 fallback 到拼接:'GRANT ' || ora_sysevent || ' ON ' || ora_dict_obj_owner || '.' || ora_dict_obj_name || ' TO ' || ora_login_user - 时间戳统一用
SYSTIMESTAMP,不用CURRENT_DATE或SYSDATE(后者无毫秒,且受会话时区影响)
日志表设计与权限陷阱
审计表必须对触发器所在 Schema 有 INSERT 权限,且不能依赖同义词或视图——触发器以定义者权限运行,只认物理对象。
- 表结构建议含:
opertime TIMESTAMP WITH TIME ZONE(带时区)、operation VARCHAR2(30)(存ORA_SYSEVENT)、object_owner VARCHAR2(128)(存ORA_DICT_OBJ_OWNER)、object_name VARCHAR2(128)、sql_stmt CLOB、os_user VARCHAR2(128)(用SYS_CONTEXT('USERENV', 'OS_USER'))、ip_address VARCHAR2(40)(用SYS_CONTEXT('USERENV', 'IP_ADDRESS')) - 如果日志表建在
AUDIT_SCHEMA,而触发器建在SCOTT下,则必须执行:GRANT INSERT ON AUDIT_SCHEMA.audit_ddl TO SCOTT,否则抛ORA-00604(递归 SQL 错误) - 避免在触发器里调用
DBMS_OUTPUT.PUT_LINE做调试——生产环境通常关着SERVEROUTPUT,你以为没输出,其实是被吞了
BEFORE vs AFTER:别想在 BEFORE 里阻止所有操作
BEFORE DDL 可中止操作(用 RAISE_APPLICATION_ERROR),但仅限于 DROP、ALTER 等少数事件;CREATE 的 BEFORE 阶段 Oracle 不允许抛异常(会报 ORA-30511)。所以“禁止删表”逻辑只能放 BEFORE DROP,而“记录所有 DDL”必须用 AFTER。
- 想拦截特定表删除?写法是:
IF ora_dict_obj_owner = 'SCOTT' AND ora_dict_obj_name IN ('EMP', 'DEPT') THEN RAISE_APPLICATION_ERROR(-20001, '禁止删除核心表'); END IF; - 不要在
BEFORE里执行EXECUTE IMMEDIATE或DBMS_DDL——Oracle 明确警告这会导致不可预测行为 -
AFTER触发器里写日志失败(如磁盘满、表空间不足),整个 DDL 语句仍会成功提交,日志丢失但业务不受影响;这是可接受的权衡
最易被忽略的一点:触发器里的所有字面量字符串(比如表名、Schema 名)必须用单引号,且大小写要和实际对象名严格一致——Oracle 默认大写,但如果你的 Schema 是小写加双引号建的(如 "scott"),那 ora_dict_obj_owner 返回的就是小写,硬编码 'SCOTT' 就永远不匹配。











