包级变量是会话级共享变量,非全局;声明于PACKAGE BODY声明段,生命周期绑定当前会话,不同会话间完全隔离,重编译包体会触发ORA-04068并清空状态。

包级变量不是全局变量,但能跨过程共享
Oracle 11g 中的包级变量(即声明在 PACKAGE BODY 声明区、不在任何子程序内部的变量)生命周期绑定于会话(session),只要同一个数据库会话没断开,所有该包内的过程、函数都能读写它。这不是“全局”意义上的共享(跨会话不互通),而是“单会话内跨过程状态保持”的核心机制。
常见错误现象是:调用过程 A 设置了变量值,再调用过程 B 却读到 NULL 或旧值——这通常是因为两次调用发生在不同会话(比如 Web 应用每次请求新建连接),或误把变量声明在了过程内部(变成局部变量)。
- 包级变量必须声明在
PACKAGE BODY的声明段,不能在PACKAGE头里声明(头里只允许类型、常量、游标、子程序签名) - 变量初始化只在包首次被引用时执行一次,后续同会话内重复调用不会重置
- 不同用户会话之间完全隔离,A 用户改了变量,B 用户完全感知不到
- 如果使用连接池(如 UCP、DRCP),要注意连接复用可能让“看似不同请求”实际落在同一会话上,导致意外状态残留
避免 ORA-04068:包状态失效后变量丢失
当包体被重新编译(CREATE OR REPLACE PACKAGE BODY),当前所有已加载该包的会话中,其包级变量状态会被清空,下次调用包内任意子程序时触发 ORA-04068: existing state of packages has been discarded 错误。这不是 bug,是 Oracle 的安全机制——防止旧代码继续用已被修改的内存布局。
典型场景:开发过程中热更新包体,而应用还在跑,第一个调用就会报这个错,且后续调用中变量值是未初始化的默认值(如 NUMBER 为 NULL)。
- 生产环境应避免直接
REPLACE包体;推荐先用ALTER PACKAGE ... COMPILE BODY,失败时可回退 - 若必须替换,应用层需捕获
ORA-04068并主动重试一次(此时包已重载,变量也完成初始化) - 不要依赖包级变量做关键业务状态(如事务中间态),它不适合做持久化或协调跨会话逻辑
包级变量 vs 过程参数:传值还是共享?
当多个过程需要协同操作同一组数据,有人倾向用 IN/OUT 参数层层传递,但链路一长就容易出错;而包级变量提供更简洁的状态载体。不过二者语义完全不同:
- 用参数传递:显式、可控、无副作用,适合一次性协作(如主过程调用校验过程再调用落库过程)
- 用包级变量:隐式、会话级延续,适合“分步操作”场景(如订单创建页先存草稿 ID,提交页再补全信息)
- 混合使用很危险:比如过程 A 把值写进包变量,过程 B 读取并修改,但过程 C 又依赖原始值——这种耦合会让调试变得极其困难
- 务必在包文档或注释里明确标注哪些变量是“状态寄存器”,哪些是“缓存临时值”,否则半年后你自己都看不懂
替代方案:什么时候不该用包级变量?
包级变量看着方便,但很多真实问题它根本解决不了。比如要跨会话共享计数器、实现分布式锁、或记录审计时间戳,硬用包变量只会埋坑。
容易被忽略的关键点是:它无法被 SQL 查询直接访问(不像表或视图),也不能被其他会话观察或干预。一旦需求涉及“可见性”“一致性”“持久性”,就得立刻转向其他机制:
- 需要跨会话共享?用表(
global_sequence表)或DBMS_ALERT - 需要高并发安全计数?用序列(
SEQUENCE)+RETURNING子句 - 需要记录操作痕迹?用触发器 + 审计表,别靠包变量攒日志
- 要防重复提交?用唯一约束或
SELECT FOR UPDATE,而不是靠包变量标记“已处理”











