oracle 阻止 drop 等高危 ddl 必须使用 on database 级别 ddl 触发器,结合 ora_dict_obj_name()、ora_login_user 和 ora_sysevent 实现细粒度控制,禁止全表拦截、禁用递归查询、必须用 raise_application_error 抛错,并单独处理 truncate、drop partition 和 drop user。
oracle 不能靠 grant/revoke 拦住 drop,必须用 ddl 触发器;但触发器本身不区分“谁删”“删什么”,硬拦会卡死归档和运维,得结合 ora_dict_obj_name()、ora_login_user 和 ora_sysevent 做细粒度判断。
DDL触发器必须建在 DATABASE 级别才能捕获所有用户操作
建在 SCHEMA 级别的触发器只对当前 schema 生效,普通用户删自己 schema 的表照样成功。真正起作用的是 ON DATABASE 级别的系统级触发器,它由 SYS 或具备 ADMINISTER DATABASE TRIGGER 权限的账号创建:
- 必须用
/ AS SYSDBA登录后创建,普通用户即使有CREATE TRIGGER权限也无法建ON DATABASE触发器 - 触发器里不能查业务表(比如
SELECT COUNT(*) FROM audit_log),否则可能引发递归 SQL 或锁等待 -
TRUNCATE在 Oracle 10g+ 会被ON DATABASE触发器捕获,但 8i/9i 不支持,需额外权限管控
只拦高危对象,放过合法操作
全表拦截 DROP 会导致归档脚本、部署任务失败。实际要按对象类型、名称、执行者动态放行:
- 用
ora_dict_obj_type IN ('TABLE', 'INDEX', 'VIEW')过滤目标类型,避免误拦SYNONYM或SEQUENCE - 用
LOWER(ora_dict_obj_name()) NOT IN ('tmp_log', 'staging_data')白名单放行临时表 - 检查
ora_login_user NOT IN ('SYS', 'SYSTEM', 'dba_admin'),保留 DBA 紧急操作通道 - 不推荐依赖
USERENV('HOST')或 IP——容器或连接池下不可靠;也不要用CURRENT_USER,因常被统一账号代理
错误必须用 raise_application_error 抛出,且错误码要规范
只写 DBMS_OUTPUT.PUT_LINE 或空 RETURN 不会中断 DDL,语句照常执行。必须调用 raise_application_error 并带明确错误号:
- 错误号建议用
-20001到-20999范围,避开 Oracle 内置错误(如-1是 ORA-00001) - 消息体里至少包含
ora_sysevent(如'DROP TABLE')、ora_dict_obj_owner和ora_dict_obj_name,方便审计定位 - 示例:
raise_application_error(-20001, 'Blocked: ' || ora_sysevent || ' on ' || ora_dict_obj_owner || '.' || ora_dict_obj_name); - 不要在触发器里加
EXCEPTION WHEN OTHERS THEN NULL——这会让错误静默吞掉,等于没拦
TRUNCATE 和 DROP PARTITION 需单独处理
TRUNCATE TABLE 和 ALTER TABLE ... DROP PARTITION 属于 DDL,但部分旧版触发器逻辑漏判。关键点:
-
ora_sysevent = 'TRUNCATE'在 10g+ 可捕获,但 9i 及更早版本不触发,需配合READ ONLY表空间或回收站(RECYCLEBIN)兜底 - 分区操作要检查
ora_dict_obj_type = 'TABLE'且语句含DROP PARTITION字样,但 Oracle 不提供直接解析 SQL 文本的函数,稳妥做法是把重点分区表名加入保护白名单,匹配ora_dict_obj_name()后再拦 -
DROP USER不触发该类触发器,需单独用BEFORE DROP USER ON DATABASE拦截,否则用户连带其下所有表一起消失
最易被忽略的是:触发器启用后,ALTER TRIGGER ... DISABLE 必须由 SYS 执行,且禁用期间无任何审计日志——生产环境若为上线临时放开,务必记录时间、操作人、原因,并在 5 分钟内重新启用。否则那几分钟就是裸奔窗口。











