必须使用ON DATABASE级别触发器才能捕获所有用户的DROP操作,且须由SYS或具备ADMINISTER DATABASE TRIGGER权限的用户创建;需结合ora_sysevent、ora_dict_obj_owner等动态判断放行条件,并用raise_application_error(-20001, ...)中止操作。
必须用 ON DATABASE 级别触发器,SCHEMA 级别无效
普通用户删自己 schema 的表,on schema 触发器完全拦不住——它只对当前 schema 的 ddl 生效,而用户执行 drop table t1 时,触发器在自己的 schema 下运行,根本不会被调用。真正能捕获所有用户的 drop 操作的,只有 on database 级别触发器,且必须由 sys 或具备 administer database trigger 权限的账号创建。
常见错误是用普通 DBA 账号(比如 SYSTEM)直接建,结果触发器不生效;必须显式以 / AS SYSDBA 登录后执行 CREATE OR REPLACE TRIGGER ... BEFORE DROP ON DATABASE。
判断逻辑要细粒度,不能一刀切拦 DROP
全表拦截 DROP 会让归档脚本、部署任务、分区维护全部失败。实际要结合 ora_sysevent、ora_dict_obj_owner、ora_dict_obj_name 和 ora_login_user 动态放行:
-
ora_dict_obj_type IN ('TABLE', 'INDEX', 'VIEW')—— 只拦关键对象,放过SEQUENCE、SYNONYM等非核心对象 -
ora_dict_obj_owner IN ('PROD_SCHEMA_A', 'PROD_SCHEMA_B')—— 明确限定目标 schema,避免误伤其他环境 -
ora_login_user NOT IN ('SYS', 'SYSTEM', 'dba_admin')—— 给 DBA 留紧急通道,否则出事没法救 -
LOWER(ora_dict_obj_name()) NOT LIKE '%_tmp' AND LOWER(ora_dict_obj_name()) NOT LIKE '%_bak'—— 允许删临时表,不然开发跑不通测试
TRUNCATE 也要单独处理,且版本兼容性要注意
TRUNCATE TABLE 在 Oracle 10g 及以上会被 ON DATABASE 触发器捕获,但 8i/9i 不支持,必须额外管控。即使在新版本,也不能依赖 ora_sysevent = 'TRUNCATE' 就完事:
因为 TRUNCATE 实际触发的是 ora_sysevent = 'DDL',且 ora_dict_obj_type 仍是 'TABLE',所以得在条件里显式加判断:
IF ora_sysevent IN ('DROP', 'TRUNCATE') AND ora_dict_obj_type = 'TABLE' THEN ...
更稳妥的做法是把 TRUNCATE 和 DROP 放到同一分支里统一拦截,再按需放行。
错误必须用 raise_application_error 中断,DBMS_OUTPUT 不起作用
写 DBMS_OUTPUT.PUT_LINE('no drop') 或空 RETURN 完全无效——DDL 会照常执行。唯一能中止操作的是 raise_application_error,且必须满足两个条件:
- 错误号在
-20001到-20999范围内(避开 Oracle 内置错误码) - 消息体包含可审计字段,例如:
'Blocked: ' || ora_sysevent || ' on ' || ora_dict_obj_owner || '.' || ora_dict_obj_name - 绝对不能加
EXCEPTION WHEN OTHERS THEN NULL,否则错误被静默吞掉,等于没拦
最简有效写法就是:raise_application_error(-20001, 'Blocked: ' || ora_sysevent || ' on ' || ora_dict_obj_owner || '.' || ora_dict_obj_name);
USERENV('HOST') 或 CURRENT_USER 是高频翻车点,生产环境上线前务必验证连接池、代理账号、容器 IP 场景下的行为。











