ALTER PACKAGE ... COMPILE BODY 卡住并引发 ORA-00060 的根本原因是:正在执行该包体的活跃会话持有行锁或未提交事务,与编译请求所需的排他型 DDL 锁形成循环等待;需通过 v$locked_object 与 v$session 关联查询定位阻塞会话,并在业务低峰期、无活跃调用时操作。

包体重新编译本身不会直接导致死锁,但会触发隐式 DDL 锁(DDL lock),而正在执行该包内函数的活跃会话若持有行级锁或未提交事务,就极易与编译请求形成循环等待 —— 这才是死锁的真正源头。
为什么 ALTER PACKAGE ... COMPILE BODY 会卡住甚至引发 ORA-00060?
Oracle 对正在被调用的 PL/SQL 包体加的是“排他型 DDL 锁”(Exclusive DDL lock),它要求:所有已打开该包的会话必须完成当前调用、释放包状态(包括任何隐式持有的游标和事务上下文),编译才能开始。如果某个会话正执行 pkg.do_something(),且内部包含未提交的 UPDATE 或长时间运行的查询,它就一直持有着表上的行锁或事务锁;而编译请求又在等它“松手”,反过来这个会话也在等编译完成才能继续(某些场景下包重载或依赖刷新会阻塞后续调用)。双方互等,Oracle 检测到后抛出 ORA-00060 并回滚其中一个事务。
- 不是所有包编译都这样 —— 只有当包体被“正在使用中”时才会触发此锁升级
-
CREATE OR REPLACE PACKAGE BODY和ALTER PACKAGE ... COMPILE BODY行为一致,都会申请 DDL 锁 - 包规范(
PACKAGE)单独重编译通常不阻塞,除非有依赖它的包体正被调用
如何快速定位是哪个会话在“挡路”?
别猜,直接查 v$locked_object 和 v$session 关联视图,重点看是否涉及 PL/SQL 相关对象:
SELECT s.sid, s.serial#, s.username, s.status, s.machine, s.program,
o.object_name, o.object_type, l.locked_mode
FROM v$locked_object l
JOIN dba_objects o ON l.object_id = o.object_id
JOIN v$session s ON l.session_id = s.sid
WHERE o.object_type IN ('PACKAGE BODY', 'PACKAGE');
- 如果
object_type = 'PACKAGE BODY'且locked_mode = 3(Row Exclusive)或6(Exclusive),说明该会话正在执行或刚执行完这个包体 - 配合
s.status = 'ACTIVE'和s.sql_id查其正在跑的 SQL,大概率能看到CALL或EXEC包内过程 - 注意
v$session.blocking_session字段,常指向那个卡在ALTER PACKAGE的 DBA 会话
生产环境安全重编译的实操原则
核心是「不让活会话和 DDL 请求正面撞上」:
- 避开业务高峰,优先选在应用空闲期(如凌晨低峰或维护窗口)操作
- 提前通知应用方暂停调用该包,或让服务端做 graceful shutdown(比如 Spring Boot 的
/actuator/shutdown) - 用
SELECT COUNT(*) FROM v$session WHERE program LIKE '%pkg_name%'快速确认当前无活跃调用 - 万不得已要强编译?先尝试加
NOWAIT:ALTER PACKAGE pkg_name COMPILE BODY NOWAIT—— 失败立即报ORA-00054而非死等,避免拖垮整个会话队列 - 长期方案:把高频更新的逻辑拆到 SQL 层或独立存储过程,包体只做轻量封装;对关键包启用
EDITION-BASED REDEFINITION实现热切换
最常被忽略的一点:开发人员习惯本地测试完立刻 COMPILE BODY,却忘了生产库上可能有后台 Job、ETL 脚本或监控探针在每分钟调用该包 —— 这些“看不见的调用者”才是死锁真正的伏笔。











