包规范只能放接口声明,包括function/procedure声明、公共类型定义、常量及authid声明;严禁执行逻辑。包体必须实现规范中所有声明,修改时应先改包体再改规范以避免阻塞调用;包变量具会话级生命周期,易引发并发风险。

包的规范(SPEC)里只能放什么
包规范是接口层,只暴露调用者需要知道的东西。它不能包含实现细节,否则会破坏封装性,也容易引发编译依赖错误。
必须放在 PACKAGE 规范里的内容只有:
- 对外可见的
FUNCTION和PROCEDURE声明(含参数名、类型、IN/OUT方向) - 公共类型定义,比如
RECORD、TABLE类型或REF CURSOR - 常量声明(
c_max_dept_id CONSTANT NUMBER := 999) -
AUTHID CURRENT_USER或AUTHID DEFINER安全上下文声明
以下内容严禁出现在规范中:DECLARE 块、变量赋值、游标体、SQL 语句、异常处理块、任何执行逻辑。一旦误写,DBeaver 或 SQL*Plus 会报 PLS-00103: Encountered the symbol "BEGIN" 类错误。
包体(BODY)里必须实现所有规范声明
包体是规范的“兑现承诺”。只要规范里声明了某个函数或过程,包体就必须提供完整实现,否则编译失败,错误信息通常是 PLS-00323: subprogram or cursor 'XXX' is declared in a package specification and must be defined in the package body。
常见疏漏点:
- 函数声明了返回类型(如
RETURN VARCHAR2),但包体里漏写RETURN语句,或返回类型不一致 - 过程参数名在规范和包体中大小写不一致(Oracle 默认不区分,但某些 IDE 会校验)
- 用了
%ROWTYPE类型但基表结构已变更,导致包体编译时报PLS-00320 - 包体中引用了规范未声明的私有过程——可以,但不能被外部调用
示例:规范中声明 FUNCTION get_employee(p_emp_id NUMBER) RETURN emp_rec_type;,包体里必须有对应函数体,且最后一行必须是 RETURN v_result;,不能只写 v_result := ...;。
修改包时,先改包体再改规范
这是避免阻塞线上调用的关键顺序。因为规范是接口契约,一旦改动(比如删函数、改参数),所有依赖它的应用或 SQL 都会立刻失效;而包体更新不影响接口,数据库自动重编译调用方代码(只要签名没变)。
典型安全操作流:
- 新增功能:先在包体里加私有函数,再在规范里追加公开声明,最后编译规范 → 包体
- 修复 bug:直接修改包体,重新编译包体即可(
ALTER PACKAGE employee_mgmt COMPILE BODY) - 删除旧接口:先注释掉规范中的声明并保留一段时间,等确认无调用后再彻底删除;切勿直接删规范再编译
- 参数调整:如果必须改参数,优先用
DEFAULT值兼容旧调用,例如把p_dept_id NUMBER改成p_dept_id NUMBER DEFAULT NULL
注意:DBeaver 中右键包 → “编译” 默认只编译包体;要编译规范,得单独右键选“编译 Specification”。
包变量生命周期与并发风险
包级变量(在包体 DECLARE 段定义的变量)是会话级持久的,不是每次调用都重置。这点极易被忽略,导致状态污染。
比如这个常见陷阱:
CREATE OR REPLACE PACKAGE BODY employee_mgmt IS
g_last_updated DATE; -- 包变量,会话内一直存在
PROCEDURE update_salary(...) IS
BEGIN
g_last_updated := SYSDATE;
...
END;
FUNCTION get_last_update RETURN DATE IS
BEGIN
RETURN g_last_updated; -- 返回的是本会话最后一次调用的时间,不是当前调用时间
END;
END;
后果:多个业务并发调用该包时,g_last_updated 可能被覆盖,get_last_update 返回不可预期的值。
解决方式:
- 尽量不用包变量存储业务状态;真需要,加会话标识(如
SYS_CONTEXT('USERENV', 'SESSIONID'))做隔离 - 用
AUTHID CURRENT_USER时更需谨慎,不同用户共享同一包实例但变量不隔离 - 若必须用,每次使用前显式初始化,别依赖“上次值”
真正稳定的共享状态,应该存表或缓存,而不是靠包变量撑着。











