必须组合使用数据库级after ddl触发器、enable_ddl_logging参数和dba_hist_active_sess_history视图:前者实时记录并可轻量拦截,后者确保捕获shutdown abort等绕过触发器的操作,补漏视图用于awr周期外的sql_opcode回溯。
不能只靠一个触发器就完整监控dba用户的ddl行为——必须拆解为“拦截+记录+补漏”三层动作,否则会漏掉 shutdown abort、本地连接、自治事务内ddl 等关键场景。
为什么 BEFORE DDL 触发器对 DBA 用户天然不可靠
DBA 账号(如 SYS、SYSTEM)默认拥有 ADMINISTER DATABASE TRIGGER 权限,可绕过普通 DDL 触发器;更关键的是:ORA_DICT_OBJ_SQL 在 SYS 下常为空,ORA_SYSEVENT 在部分本地连接中也不稳定。直接建 BEFORE CREATE ON DATABASE 对 DBA 几乎无效。
- 本地连接(
sqlplus / as sysdba)时ORA_CLIENT_IP_ADDRESS为NULL,必须改用ORA_SYS_CONTEXT('USERENV', 'HOST')或ORA_SYS_CONTEXT('USERENV', 'OS_USER') -
CREATE OR REPLACE PROCEDURE这类语句在 PL/SQL 块内执行时,不会触发数据库级 DDL 触发器 - 触发器自身不能查
v$session或v$sql(会报ORA-04092),但可以安全读USERENV上下文
必须组合使用:数据库级 DDL 触发器 + ENABLE_DDL_LOGGING + 补充审计视图
三者不是替代关系,而是覆盖不同盲区:
-
ENABLE_DDL_LOGGING = TRUE是基础开关,开启后所有 DDL(含SYS)都会写入$ORACLE_BASE/diag/rdbms/<db_name>/<inst_name>/log/ddl</inst_name></db_name>目录下的独立日志文件,字段包含时间、用户、SQL 文本——这是唯一能捕获SHUTDOWN ABORT前最后一条 DDL 的方式 - 数据库级 DDL 触发器(
AFTER CREATE OR ALTER OR DROP ON DATABASE)用于实时入库、发通知、或做轻量判断(如阻断非白名单对象名),但需注意:ORA_LOGIN_USER在 DDL 触发时可能不等于实际执行者(比如通过DEFINER RIGHTS过程调用),应优先用ORA_DICT_OBJ_OWNER和ORA_SYSEVENT - 补漏用
DBA_HIST_ACTIVE_SESS_HISTORY:当触发器或日志没捕获到时,靠SQL_OPCODE反查(DROP TABLE=12、TRUNCATE TABLE=85),但依赖 AWR 快照周期,最多滞后 1 小时
实操:给 SYS 和 SYSTEM 单独建带 host/IP 校验的 DDL 记录触发器
以下触发器不阻断操作,只确保每条 DDL 都落库,且能区分真实来源:
CREATE OR REPLACE TRIGGER tr_dba_ddl_log
AFTER CREATE OR ALTER OR DROP ON DATABASE
DECLARE
v_user VARCHAR2(30) := SYS_CONTEXT('USERENV', 'SESSION_USER');
v_host VARCHAR2(100) := SYS_CONTEXT('USERENV', 'HOST');
v_ip VARCHAR2(32) := SYS_CONTEXT('USERENV', 'IP_ADDRESS');
v_osuser VARCHAR2(30) := SYS_CONTEXT('USERENV', 'OS_USER');
v_sql CLOB;
BEGIN
-- 只记录目标用户,避免日志爆炸
IF v_user IN ('SYS', 'SYSTEM') THEN
-- 拼接 SQL(注意:ORA_DICT_OBJ_SQL 在 SYS 下常为空,这里兜底用动态拼)
v_sql := ora_sysevent || ' ' ||
NVL(ora_dict_obj_type, '?') || ' ' ||
NVL(ora_dict_obj_owner, '?') || '.' ||
NVL(ora_dict_obj_name, '?');
<pre class="brush:php;toolbar:false;">INSERT INTO ddl_log (
oper_user, oper_host, oper_ip, oper_osuser,
oper_action, db_obj_type, db_obj_owner, db_obj_name,
oper_date, sql_text
) VALUES (
v_user, v_host, v_ip, v_osuser,
ora_sysevent, ora_dict_obj_type, ora_dict_obj_owner, ora_dict_obj_name,
SYSDATE, v_sql
);END IF; EXCEPTION WHEN OTHERS THEN NULL; -- 避免触发器失败导致 DDL 失败 END;
- 表
ddl_log必须提前建好,字段含oper_host和oper_osuser,因为IP_ADDRESS在本地连接下不可信 - 触发器里不要调用
UTL_FILE或发邮件——AFTER DDL中 IO 或网络耗时会导致 DDL 响应卡顿 -
WHEN OTHERS THEN NULL不是偷懒,而是防止日志表不可用时连带让 DDL 报错
最容易被忽略的三个点
生产环境出问题,往往卡在这三个地方:
-
ENABLE_DDL_LOGGING开启后需重启实例才生效(12c+ 可在线改,但老版本必须重启),很多人改完参数没重启,以为开了其实没开 -
DBA_HIST_ACTIVE_SESS_HISTORY默认只保留 7 天数据,误操作发生后超过这个时间就查不到SQL_OPCODE记录 - 触发器里用
SYSDATE记录时间,但 Oracle 实际执行 DDL 的时间点可能比触发器启动晚几毫秒,高并发时两条相近 DDL 的时间戳可能颠倒











