根本原因是存储过程被反复硬解析,每次调用生成新句柄并挤占同一hash bucket,引发mutex争用;其编译/调用需获取library cache lock以保护对象定义,多会话并发解析同一过程时即触发锁等待。
根本原因不是并发量大,而是存储过程被反复硬解析——每次调用都生成新句柄,挤在同一个 library cache hash bucket 里抢 mutex。
为什么存储过程编译/调用会触发 Library Cache Lock
Oracle 在执行存储过程前必须完成解析(parse),而解析过程要获取 Library Cache Lock 来保护对象句柄(handle)。这个锁不是锁表、也不是锁数据,而是锁“存储过程的定义本身”。只要多个会话同时尝试解析同一个存储过程,就会争抢同一 handle 上的 lock。
常见错误现象包括:ORA-04021: timeout occurred while waiting to lock object、会话长时间卡在 library cache lock 等待事件、AWR 中该事件排进 Top 5。
- DDL 修改后首次调用:ALTER PROCEDURE 后,所有后续调用都会触发重新验证和软解析,但若 shared pool 不足或游标失效频繁,就退化为硬解析
- 未使用绑定变量的动态 SQL:比如在过程体内拼接
'SELECT * FROM t WHERE id = ' || v_id,每次执行都算一条新 SQL,连带触发过程体内的重复解析 - 自动统计信息收集(
auto optimizer stats collection)运行期间:它会让依赖对象的游标失效,导致下一次调用必须重新 parse 存储过程
如何确认是存储过程自身引发的锁争用
不能只看等待事件,得定位到具体对象。关键查 v$db_object_cache 和 x$kgllk:
- 查哪些过程正被高频率锁定:
SELECT owner, name, type, locks, pins FROM v$db_object_cache WHERE type = 'PROCEDURE' AND locks > 0 ORDER BY locks DESC - 查谁在 blocking:
SELECT kgllkses saddr, kgllkhdl handle, kgllkmod mod, kglnaobj object FROM x$kgllk WHERE kgllkmod > 0 AND kglnaobj LIKE '%YOUR_PROC_NAME%' - 结合
v$session查 blocking 会话的program和sql_opname:如果是PLSQL或PARSE,基本可锁定是过程编译行为本身;如果是jdbc thin client+ 大量EXEC,说明应用层在高频重编译
真正有效的缓解动作都在应用和开发侧
DBA 调 shared_pool_size、开 cursor_sharing、flush shared_pool,都是临时止痛。根治必须落在代码和调用方式上:
- JDBC 场景:确保
CallableStatement复用,避免每次 new 一个;禁用implicitCache,并在 execute 前调用clearParameters() - PL/SQL 内部调用:把动态 SQL 改成静态,或至少用
EXECUTE IMMEDIATE ... USING绑定变量,杜绝字符串拼接 - 避免在循环内反复
CREATE OR REPLACE PROCEDURE或ALTER PROCEDURE—— 这会强制 invalidate 所有依赖游标 - 对高频核心过程,考虑用
DBMS_SHARED_POOL.KEEP钉住其句柄,防止被 aging out(但仅限小对象,别乱 keep 大包)
容易被忽略的 RAC 特殊情况
RAC 下,Library Cache Lock 的协调由 LCK0 进程代理,等待超时默认 60 秒(单实例是 3 秒)。这意味着:一个节点上因 DDL 卡住的 lock,在其他节点看起来是“长时间无响应”,而非快速报错。更麻烦的是,如果某个过程频繁被跨实例调用(比如服务网关统一走 node1 连接池),所有节点的解析请求最终都会打到同一段 shared pool 内存区域,加剧 hash bucket 冲突。
此时光看单节点 AWR 没用,必须用 GV$ACTIVE_SESSION_HISTORY 聚合所有实例,按 p1(handle address)分组统计等待次数——真正的热点 handle 往往藏在跨实例聚合结果里。











