ora-04068 是 oracle 有状态包重编译后会话状态丢失的必然反应;只要包含全局变量/常量,重编译即触发该错,且首次调用必现,需在 jdbc 层精准识别并单次重试,或改造为无状态包根治。

ORA-04068 不是数据库故障,而是 Oracle 会话缓存机制的必然反应——只要包里定义了任何全局变量或常量,重编译后旧连接必然报错,且第一次调用必现。
为什么改个包体就炸:有状态包 vs 无状态包
Oracle 把包里所有 DECLARE 在包规范或包体顶层的变量、常量、游标,统称为“包状态”。一旦存在这类声明,该包就是“有状态包”(stateful package)。每次重编译(CREATE OR REPLACE PACKAGE 或 ALTER PACKAGE ... COMPILE),Oracle 就会丢弃所有已建立连接中该包的本地状态副本。下次调用时,会话发现状态没了,立刻抛 ORA-04068,并连带 ORA-04061 和 ORA-06508。
典型触发点包括:
- 开发用 PL/SQL Developer 编译了
PKG_COMMON,Java 应用还在用旧连接池里的连接 - 定时 Job 执行了
DROP TABLE+CREATE TABLE,导致依赖该表的包元数据刷新,隐式触发状态丢弃 - RAC 环境下节点间时间不同步,
STIME与P_TIMESTAMP不一致,被 Oracle 视为不安全状态而丢弃
JDBC 层自动重试是最稳的兜底方案
重启应用或杀 session 是临时止痛,线上不可行;禁止改包也不现实。真正可控的做法,是在 JDBC 异常拦截层识别 ORA-04068 并透明重试一次——因为第二次调用时,Oracle 已重建包状态,必然成功。
判断逻辑必须严格:
- 错误码必须是
4068,SQLState 必须是72000(不是所有驱动都返回这个,但 Oracle 官方 JDBC 驱动稳定返回) - 不能对所有 SQLException 重试,只针对明确的
ORA-04068 - 重试应限于单次,避免无限循环(比如真遇到网络中断或权限问题)
示例伪代码逻辑:
if (e.getErrorCode() == 4068 && "72000".equals(e.getSQLState())) {
// 记录 warn 日志,说明是包状态重置
return executeOnceMore(); // 重试原 SQL 或存储过程调用
}
从根上避免:让包彻底无状态
如果能改造包,这是最彻底的解法。Oracle 对“无状态包”(stateless package)完全不维护运行时副本,重编译不会引发任何会话级异常。
做到无状态的关键是:
- 删掉所有包规范和包体顶层的
VARIABLE、CONSTANT、CURSOR声明 - 把常量挪到单独的“常量包”里(如
PKG_CONSTANTS),且该包自身不带任何变量——它只是纯定义,重编译不影响其它包调用 - 若必须维持跨过程共享值,改用数据库表、
DBMS_SESSION.SET_CONTEXT或应用层缓存,而非包变量
注意:FUNCTION 或 PROCEDURE 内部声明的局部变量不算“包状态”,它们每次调用都新建销毁,完全安全。
排查依赖包时间戳不一致(RAC/多节点场景)
在 RAC 或多个应用节点共用同一套包的环境里,ORA-04068 可能不是因为有人手动编译,而是节点间包依赖的时间戳不同步。Oracle 发现 STIME != P_TIMESTAMP 会主动丢弃状态。
快速定位命令:
SELECT do.name d_name, po.name p_name,
to_char(po.stime, 'DD-MON-YYYY HH24:MI:SS') "STIME",
to_char(p_timestamp, 'DD-MON-YYYY HH24:MI:SS') "P_Timestamp",
decode(sign(po.stime - p_timestamp), 0, 'SAME', '*DIFFER*') X
FROM sys.dependency$ d, sys.obj$ do, sys.obj$ po
WHERE d.p_obj# = po.obj# AND d.d_obj# = do.obj#
AND po.stime != p_timestamp;
查出差异后,统一在主节点执行 ALTER PACKAGE pkg_name COMPILE 即可同步时间戳。老版本 Oracle(如 11.2.0.2)存在 bug #13328947,打补丁 9681133 可修复。
真正难处理的从来不是报错本身,而是包里那个看似无害的 C_CODE_SUCCESS CONSTANT VARCHAR2(10) := 'SUCCESS'——它让整个包变成有状态,也让所有复用连接的客户端暴露在重编译风险之下。是否保留它,得权衡“开发便利性”和“生产稳定性”。











