Oracle DDL触发器必须为BEFORE DROP和AFTER ALTER分别创建,不可混用;需用ora_dict_obj_owner和ora_dict_obj_name判断对象,禁用USER_*视图查字典;BEFORE中禁止EXECUTE IMMEDIATE。

BEFORE DROP和AFTER ALTER必须分开写,不能混用一个触发器
Oracle的DDL触发器不支持用单个触发器捕获所有变更类型。比如想同时拦DROP TABLE和记录ALTER TABLE,得分别建两个触发器:一个监听BEFORE DROP(用于阻止),一个监听AFTER ALTER(用于日志)。很多人误以为AFTER DDL能覆盖全部,结果BEFORE DROP根本没生效——因为Oracle把每个DDL事件当作独立类型处理。
实操建议:
-
BEFORE DROP触发器里可调用RAISE_APPLICATION_ERROR(-20001, '禁止删除')中止操作;AFTER ALTER只能记录,无法回滚 - 别在同一个触发器里写
IF ora_sysevent = 'DROP' THEN ... ELSIF ora_sysevent = 'ALTER' THEN ...——语法合法但逻辑危险:一旦RAISE_APPLICATION_ERROR在ALTER分支执行,整个ALTER会被意外中断 - 触发器必须建在目标Schema下(如SCOTT),不是SYS下建一个就能管全库
ora_dict_obj_name和ora_dict_obj_owner是唯一可信的上下文变量
不要用SELECT COUNT(*) FROM USER_TABLES WHERE TABLE_NAME = 'EMP'去判断表是否存在。DDL触发器运行时,对象可能已部分删除,USER_TABLES查不到反而放行。Oracle在触发时自动注入ora_dict_obj_name和ora_dict_obj_owner,它们是只读、实时、无需权限校验的绑定变量。
实操建议:
- 禁止删核心表的逻辑必须用:
IF ora_dict_obj_owner = 'SCOTT' AND ora_dict_obj_name IN ('EMP', 'DEPT') THEN RAISE_APPLICATION_ERROR(-20001, ...); - 别拼接SQL动态查字典视图,尤其避免
USER_*系列——触发上下文用户可能是HR,但你要监控的是SCOTT的表 - 日志表写入时,用
SYSDATE而非CURRENT_DATE,后者受会话时区影响,归档分析时时间错乱
DBA_OBJECTS比USER_TABLES安全,但WHERE OWNER = SYS.LOGIN_USER不能少
如果真要在触发器里查数据字典(比如记录被删对象的类型),必须用DBA_OBJECTS这类全局视图,并显式加WHERE OWNER = SYS.LOGIN_USER。否则在HR用户执行DROP TABLE SCOTT.EMP时,触发器在HR上下文运行,USER_TABLES查的是HR自己的表,DBA_OBJECTS不加过滤则返回全库结果,性能爆炸且权限易报错。
实操建议:
- 查对象类型用:
SELECT object_type FROM DBA_OBJECTS WHERE OWNER = SYS.LOGIN_USER AND OBJECT_NAME = ora_dict_obj_name AND ROWNUM = 1; - 避免在触发器里做INSERT日志——除非日志表已对所有可能触发该DDL的用户授予INSERT权限,否则大概率抛
ORA-00604 - 调试时用
DBMS_OUTPUT.PUT_LINE前,确认当前会话已执行SET SERVEROUTPUT ON,否则输出被静默吞掉
BEFORE事件里严禁EXECUTE IMMEDIATE或DBMS_DDL
Oracle官方文档明确警告:在BEFORE DDL触发器中执行任何DDL(如EXECUTE IMMEDIATE 'CREATE TABLE')会导致不可预测行为,常见错误是ORA-30511(invalid DDL operation in system triggers)。这不是权限问题,是内核级限制。
实操建议:
- 所有DDL操作必须剥离到触发器外——比如由外部JOB或应用层调用,触发器只负责埋点或阻断
- 别试图在
BEFORE CREATE里自动补索引或约束,这违反原子性,也绕过权限检查 - 如果需要结构同步,用Flyway/Liquibase这类外部工具,而不是在触发器里硬编码
EXECUTE IMMEDIATE
ora_dict_obj_owner这个变量——它才是真实被操作对象的Owner,不是当前登录用户。

















