必须用系统级after ddl on database触发器,因before ddl on schema无法识别dba操作、不支持raise_application_error中止语句,且触发器内禁止dml/ddl;after触发器可精确匹配ora_dict_obj_owner和ora_dict_obj_name并安全回滚。
直接禁用对特定表的 drop、alter、truncate 操作,必须用系统级 after ddl on database 触发器配合 ora_dict_obj_name 和 ora_dict_obj_owner 精确匹配目标表——before ddl 在 oracle 11g 中无法可靠拦截(会绕过权限检查但可能引发 ora-00604)。
为什么不能用 BEFORE DDL ON SCHEMA 拦截单个表
BEFORE DDL ON SCHEMA 触发器作用于当前用户模式,但无法区分“谁在操作”和“操作哪个具体对象”,尤其当 DBA 以 AS SYSDBA 登录后,该触发器根本不会触发。更关键的是:Oracle 11g 明确限制 BEFORE DDL 不允许执行 RAISE_APPLICATION_ERROR 来中止语句(仅 AFTER 允许),强行写会导致递归错误或静默失效。
- 试图在
BEFORE触发器里raise_application_error→ 报ORA-00604: 递归 SQL 级别 1 出现错误 - 用
SELECT ... INTO查保护表列表 → 触发器内禁止 DML/DDL,查表会直接报ORA-00604+ORA-06512 - 依赖
USER或sys_context('userenv','current_user')判断操作者 → DBA 绕过时值为SYS,但触发器本身不运行
正确做法:AFTER DDL ON DATABASE + 精确对象名匹配
系统级 AFTER DDL ON DATABASE 触发器在所有 DDL 执行完毕后触发,但它能拿到完整上下文,并允许用 RAISE_APPLICATION_ERROR 回滚整个事务(Oracle 会自动回退 DDL 变更)。关键是必须用 ora_dict_obj_owner 和 ora_dict_obj_name 做大小写敏感比对——Oracle 默认对象名大写,所以你的保护列表也得大写。
- 触发器必须由
SYS用户创建,且数据库需启用系统触发器(默认开启) - 只拦截
DROP、ALTER、TRUNCATE,避开GRANT/COMMENT等无关事件 - 匹配逻辑示例:
IF ora_dict_obj_owner = 'HR' AND ora_dict_obj_name = 'EMPLOYEES' AND ora_sysevent IN ('DROP','ALTER','TRUNCATE') THEN ... - 避免用
UPPER(ora_dict_obj_name)—— 多余且可能掩盖大小写问题;直接按实际建表名写死(如'EMPLOYEES')
如何安全测试和临时关闭该触发器
这类触发器一旦出错,连 DROP TRIGGER 都可能被自己拦住。上线前务必验证关闭路径是否畅通:
- 用
SYS执行ALTER TRIGGER trigger_name DISABLE;可立即停用,无需重启 - 禁用后,用普通用户执行
ALTER TABLE protected_table ADD col NUMBER;应成功 - 重新启用:
ALTER TRIGGER trigger_name ENABLE; - 切勿在触发器里写日志表插入(
INSERT INTO audit_log)——触发器内禁止 DML,否则必报ORA-00604
真正难处理的不是写法,而是“保护表名变更”和“分区表的子对象操作”——ora_dict_obj_name 对 TRUNCATE PARTITION 返回的是分区名而非基表名,这种场景必须额外解析 ora_sql_txt 或改用白名单机制,而不是硬编码表名。











