PL/SQL包规范只放接口声明,包括过程/函数签名、全局常量、类型定义、游标声明;包体须严格匹配规范,变量按会话隔离,编译须先规范后包体并确保依赖有效。
PL/SQL 包规范(PACKAGE)里该放什么
包规范是接口声明层,只暴露调用者需要知道的东西,不是代码存放处。放错内容会导致编译失败或隐藏依赖问题。
-
PROCEDURE和FUNCTION只写签名,不写BEGIN...END - 全局常量、类型定义(
TYPE my_tab IS TABLE OF ...)、游标声明(CURSOR c1 IS SELECT ...)可以放,但别放带逻辑的初始化语句 - 变量声明要谨慎:只放真正需跨过程共享的
g_counter NUMBER := 0这类包级变量;避免放大对象(如g_blob BLOB),否则每个会话都占内存 - 别在规范里调用
DBMS_OUTPUT.PUT_LINE或其他过程——语法不允许,会报PLS-00103
包体(PACKAGE BODY)必须和规范严格匹配
包体不是“随便实现”,它和规范是强契约关系。名字、参数顺序、数据类型、返回类型一个字符都不能差,否则调用时看似成功,实际运行报错。
- 函数返回类型必须和规范中声明的完全一致,比如规范写
RETURN VARCHAR2,包体里不能写RETURN VARCHAR2(100)(长度限定在规范中无效,但包体里加长度不报错,却可能引发隐式转换问题) - 参数模式(
IN/OUT/IN OUT)必须一致;OUT参数在包体中未赋值,调用时会得NULL,但不会报错——这是最隐蔽的逻辑漏洞来源 - 包体里可以有私有子程序(未在规范中声明的
PROCEDURE),它们只能被本包内其他过程调用,适合拆分复杂逻辑,但别忘了加PRAGMA AUTONOMOUS_TRANSACTION这类特性时,必须放在包体里,规范中不能声明
编译顺序错误导致 PLS-00905: object is invalid
Oracle 要求先编译规范,再编译包体。但更常见的是:规范编译成功了,包体编译失败,之后你改了规范又没重新编译包体,就卡在“无效”状态。
- 检查当前状态:
SELECT status FROM user_objects WHERE object_name = 'MY_PKG' AND object_type = 'PACKAGE';—— 若为INVALID,说明包体没过编译 - 不要只执行
CREATE OR REPLACE PACKAGE BODY,先确认规范已有效:SELECT text FROM user_errors WHERE name = 'MY_PKG' AND type = 'PACKAGE';看是否有遗留错误 - 修改规范后,包体必须显式重编译(哪怕没动代码),否则仍用旧校验结果;可执行
ALTER PACKAGE my_pkg COMPILE BODY; - 如果包体里引用了另一个尚未编译的包(比如
other_pkg.do_something()),也会触发PLS-00905,这时要先确保依赖包已有效
包变量生命周期与会话隔离性
PL/SQL 包变量不是全局单例,而是按会话隔离的。这点常被误当成“共享内存”,结果在应用连接池场景下出现状态污染。
-
g_timestamp TIMESTAMP := SYSTIMESTAMP这种初始化只在会话首次调用该包任意元素时执行一次,后续同会话中该变量一直存在,直到会话断开 - Web 应用用连接池(如 UCP、Druid)时,一个物理连接被多个请求复用,
g_counter可能被意外累加——这不是 bug,是设计使然;若需每次清空,得在入口过程里手动重置 - 别在包变量里存大对象(如
g_clob CLOB),尤其配合DBMS_LOB操作时,容易触发ORA-04030内存耗尽,且无法被 GC 回收 - 调试时想看包变量值?用
SELECT value FROM v$sesstat s, v$statname n WHERE s.statistic# = n.statistic# AND n.name = 'session pga memory'辅助判断,但更直接的是在过程里加DBMS_OUTPUT.PUT_LINE('g_x='||g_x);并确保客户端启用了输出
包结构本身不难,难在会话边界、编译依赖、变量生命周期这三块——它们不出现在语法书里,但几乎每个线上 PL/SQL 包都会撞上










