accessible by仅在编译期限制指定程序单元调用包内子程序,不控制运行时表访问或权限;必须写在包规范的子程序声明末尾,白名单需带类型前缀且引用对象须已存在有效。

ACCESSIBLE BY能限制谁调用包里的函数或过程,但不能限制表访问
ACCESSIBLE BY不是权限控制机制,它只做编译期检查:当某个程序单元(比如另一个包、过程或函数)试图调用当前包中被标记的子程序时,如果调用方不在白名单里,CREATE OR REPLACE PACKAGE就会报错 PLS-00904: insufficient privilege to access object。它不干预运行时行为,也不影响SELECT/INSERT等DML权限——那些还得靠GRANT/REVOKE。
常见错误现象是以为加了ACCESSIBLE BY就能防止别人查数据,结果发现用户照样能绕过包直接查底层表;或者误以为它能替代EXECUTE权限,导致调用方明明有EXECUTE,却因不在白名单里而编译失败。
使用场景很明确:你有一组内部工具函数(比如log_error、validate_input),只希望被特定业务包(如pkg_order_process)调用,不希望被任意匿名块、SQL*Plus脚本或第三方应用误用。
-
ACCESSIBLE BY必须写在子程序声明处,不能放在包体实现里 - 白名单只接受程序单元名,不支持角色、用户或通配符(比如不能写
ACCESSIBLE BY ROLE app_developer) - 被引用的程序单元必须已存在且状态为VALID;若先创建带
ACCESSIBLE BY的包,再建白名单里的包,会编译失败 - 该子句对PACKAGE SPECIFICATION生效,与包体是否定义无关;即使包体为空,只要spec里声明了带
ACCESSIBLE BY的过程,检查就起作用
怎么写ACCESSIBLE BY语句才不会编译报错
语法上容易踩坑的地方集中在位置、拼写和依赖顺序。最常出错的是把ACCESSIBLE BY写在包体里,或写成ACCESSIBLE BY (pkg_a, pkg_b)漏掉PACKAGE关键字。
正确写法必须满足三个硬性条件:
- 只能出现在package specification中的子程序声明行末尾,例如:
FUNCTION get_tax_rate RETURN NUMBER ACCESSIBLE BY (PACKAGE pkg_finance); - 括号内每个条目必须带类型前缀:
PACKAGE pkg_name、PROCEDURE proc_name、FUNCTION func_name,不能省略 - 如果白名单含多个单元,用逗号分隔,但结尾不能有逗号(Oracle 21c仍不支持尾随逗号)
- 被引用的
pkg_finance必须已存在,且其spec已编译成功;否则当前包编译时会提示PLS-00302: component 'PKG_FINANCE' must be declared
示例(合法):
可完全访问 Exchange 2010 EWS,管理邮件、文件夹、附件、日历事件、联系人、任务及外出设置。
CREATE OR REPLACE PACKAGE pkg_utils AS
FUNCTION sanitize_input(p_str VARCHAR2) RETURN VARCHAR2
ACCESSIBLE BY (PACKAGE pkg_order_entry, PROCEDURE legacy_loader);
PROCEDURE log_audit(p_action VARCHAR2)
ACCESSIBLE BY (PACKAGE pkg_audit_trail);
END;
ACCESSIBLE BY和调用者权限(AUTHID CURRENT_USER)混用时的坑
两者解决的问题不同,但叠加使用时容易产生误解。ACCESSIBLE BY管“谁能声明调用”,AUTHID CURRENT_USER管“执行时以谁的权限查表”。如果一个函数同时用了ACCESSIBLE BY (PACKAGE pkg_x)和AUTHID CURRENT_USER,那意味着:只有pkg_x能编译期声明调用它,但运行时查表权限仍取决于调用者(即pkg_x的owner)是否有对应表权限。
典型问题:
- 你给
pkg_x授了SELECT ON hr.employees,但它调用的pkg_utils.sanitize_input又去查hr.departments——哪怕sanitize_input加了ACCESSIBLE BY,也拦不住权限缺失导致的运行时报错ORA-00942: table or view does not exist - 如果
pkg_utils本身用AUTHID DEFINER(默认),而pkg_x没被授EXECUTE,那么即使ACCESSIBLE BY放行,调用仍失败,错误是ORA-06550: line X, column Y: PLS-00201: identifier 'PKG_UTILS.SANITIZE_INPUT' must be declared(因为根本看不到这个函数) -
ACCESSIBLE BY不阻止动态SQL:如果pkg_x里用EXECUTE IMMEDIATE 'BEGIN pkg_utils.sanitize_input(...); END;',编译期检查会被绕过,ACCESSIBLE BY完全失效
为什么ACCESSIBLE BY无法防止DBA绕过
这是设计使然,不是缺陷。Oracle明确文档指出:ACCESSIBLE BY仅用于开发阶段的逻辑隔离,不提供安全边界。任何具有ADMINISTER DATABASE TRIGGER或ALTER ANY PROCEDURE权限的用户(通常是DBA)都能直接修改包spec,删掉ACCESSIBLE BY子句,或者把白名单加上自己的调试包。
更关键的是,它不防对象级访问。比如你有一个函数get_salary被ACCESSIBLE BY (PACKAGE pkg_hr)保护,但DBA完全可以:
- 用
SELECT get_salary(...) FROM dual直接调用(只要他有EXECUTE权限) - 查
dba_source拿到函数逻辑,然后抄一份去掉ACCESSIBLE BY重新创建 - 用
DBMS_DDL.CREATE_WRAPPED包裹后创建,虽然源码不可读,但调用限制依然消失
真正需要强访问控制的场景,应组合使用:显式GRANT EXECUTE + ACCESSIBLE BY作为第一道提醒,再配合审计策略(AUDIT EXECUTE ON pkg_utils)追踪非常规调用。别把它当成锁保险柜的密码。










