必须用after ddl系统触发器,且由dba在目标用户schema或数据库级创建;普通用户无权创建,因其ddl操作不触发表级触发器,仅系统事件触发器可捕获create/drop/alter等动作。

直接上结论:必须用 AFTER DDL 系统触发器,且需在目标用户 schema 或数据库级创建;普通用户自身无法创建或启用该类触发器,权限必须由 DBA 授予。
为什么不能用普通用户自己建的触发器监控 DDL
DDL(如 CREATE TABLE、DROP INDEX)不是针对某张表的 DML 操作,它不走表级触发器路径。普通用户即使在自己 schema 下建了 BEFORE/AFTER INSERT OR UPDATE 触发器,对 ALTER USER 或 GRANT SELECT 这类 DDL 完全无感知——这些语句根本不会触发表级触发器。
真正能捕获 DDL 的只有 Oracle 的系统事件触发器,而这类触发器:
- 必须由具有
ADMINISTER DATABASE TRIGGER权限(通常是SYS或授权过的 DBA)创建 - 作用域只能是
ON DATABASE(全库)或ON SCHEMA(单 schema),不能绑定到普通用户会话里 - 触发时机固定为
AFTER CREATE、AFTER DROP、AFTER ALTER等,不支持BEFORE(DDL 不可回滚)
如何创建有效监控的 DDL 触发器(DBA 操作)
假设你要监控用户 C##APPUSER 的所有 DDL 行为,推荐用 schema 级触发器,比数据库级更轻量、更聚焦:
第一步:确保审计表已存在(字段必须覆盖关键上下文)
CREATE TABLE ddl_audit_log ( id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, op_time TIMESTAMP WITH TIME ZONE DEFAULT SYSTIMESTAMP, db_user VARCHAR2(128), os_user VARCHAR2(128), machine VARCHAR2(64), ip_address VARCHAR2(39), operation VARCHAR2(30), object_type VARCHAR2(30), object_name VARCHAR2(128), sql_text CLOB );
第二步:DBA 执行触发器创建(注意替换 schema 名)
CREATE OR REPLACE TRIGGER tr_ddl_audit_cappuser
AFTER CREATE OR DROP OR ALTER ON C##APPUSER.SCHEMA
DECLARE
l_sql_text CLOB;
BEGIN
-- 只捕获该 schema 下的 DDL,避免误录其他用户操作
IF ORA_DICT_OBJ_OWNER = 'C##APPUSER' THEN
-- 获取完整 SQL(需开启 EVENTS 10046 或依赖 ORA_SQL_TXT,但后者有长度限制)
BEGIN
SELECT sql_text INTO l_sql_text
FROM v$sql
WHERE sql_id = (SELECT prev_sql_id FROM v$session WHERE sid = SYS_CONTEXT('USERENV','SID'))
AND ROWNUM = 1;
EXCEPTION WHEN NO_DATA_FOUND THEN l_sql_text := '[SQL not found in shared pool]';
END;
<pre class="brush:php;toolbar:false;">INSERT INTO ddl_audit_log (
db_user, os_user, machine, ip_address,
operation, object_type, object_name, sql_text
) VALUES (
SYS_CONTEXT('USERENV', 'CURRENT_USER'),
SYS_CONTEXT('USERENV', 'OS_USER'),
SYS_CONTEXT('USERENV', 'HOST'),
SYS_CONTEXT('USERENV', 'IP_ADDRESS'),
ORA_SYSEVENT,
ORA_DICT_OBJ_TYPE,
ORA_DICT_OBJ_NAME,
l_sql_text
);END IF; END;
关键点:
-
ORA_SYSEVENT给出真实动作('CREATE'、'DROP'),不是靠判断语句文本 -
ORA_DICT_OBJ_OWNER必须显式校验,否则ON SCHEMA触发器在跨 schema 操作时也会触发(比如C##APPUSER执行CREATE SYNONYM FOR HR.EMP) -
v$sql查prev_sql_id是取当前会话上一条执行的 SQL,比ORA_SQL_TXT()更可靠(后者最大只返回 1000 字符,且在某些版本中不可用)
常见错误现象和坑
你可能看到日志为空、只录了部分操作、或触发器报错 ORA-00604: error occurred at recursive SQL level 1,大概率是以下原因:
- 没给触发器所在用户(如
SYS)对ddl_audit_log表的INSERT权限:GRANT INSERT ON ddl_audit_log TO SYS; - 触发器里用了未授权的视图(如
v$session):必须显式GRANT SELECT ON v_$session TO your_trigger_owner;(注意是v_$session,不是v$session) - 试图在触发器里做复杂逻辑(如发邮件、调用外部 HTTP):DDL 触发器执行期间禁止提交/回滚,也不建议做耗时操作,否则会阻塞用户 DDL
- 误用
ON DATABASE却没加 owner 判断:结果把 DBA 自己建表、ANALYZE等全记进去了,日志爆炸
真正难的不是写触发器,而是让 sql_text 字段稳定拿到完整语句——Oracle 并不保证 DDL 的原始文本一定留在 v$sql 里,尤其短命 SQL 或被老化淘汰后。如果审计要求 100% 精确,得搭配 ENABLE DDL LOGGING(12c+)或 AWR 快照补位,不能只靠一个触发器兜底。











