前言
前面写 Oracle 等待事件那篇文章时,留言区有用户提到了 library cache,希望我能详细聊聊,其实这类等待和普通的 I/O、行锁等待不太一样。
比如 db file sequential read,可以从读取量和 I/O 延迟入手;enq: TX - row lock contention,通常先找阻塞会话。
但如果 AWR 里面出现下面几个事件:
text
library cache lock
library cache pin
library cache load lock
library cache: mutex X
cursor: pin S wait on X
排查起来就没那么直接了。
这些事件虽然都和 Library Cache 有关,但等待的资源并不相同。尤其是 library cache lock 和 library cache pin,名字只有一个单词的区别,涉及的机制却不一样。
这篇把相关原理和常用排查方法整理一下,以 Oracle 19c 为例。
一、先搞清楚 Library Cache 是什么
Oracle 的 SGA 中有一块 Shared Pool,而 Library Cache 是 Shared Pool 的重要组成部分。
SQL 解析后的游标、执行计划,以及 PL/SQL 程序单元等相关信息,都可能保存在 Library Cache 中。
比如应用执行:
sql
SELECT * FROM TEST_TABLE WHERE ID = :1;
Oracle 需要解析 SQL,查找是否存在可以复用的游标,并检查对象、权限及相关依赖。如果能够复用,就不需要每次都重新完成硬解析。
不过,这些缓存对象可能同时被多个会话使用。
假设一个会话正在执行某个存储过程,另一个会话准备重新编译这个过程,Oracle 就需要协调对相关 Library Cache 对象的访问。
这也是 Library Cache 中存在 Lock、Pin 和 Mutex 等机制的原因。
二、library cache lock 和 pin 有什么区别?
这是最容易混淆的两个事件。
- 先看
library cache lock:
它主要用于协调 Library Cache 对象句柄的访问,以及维护对象之间的依赖关系。
比如某个会话正在使用一个对象,另一个会话准备修改该对象,可能需要等待相关的 Library Cache Lock。
- 再看
library cache pin:
它主要用于保护对象在 Library Cache 中的具体内容。会话需要访问或修改对象内容时,通常需要在取得相应 Lock 后,再取得 Pin。
可以简单理解为:
- Lock 主要协调对象的访问及依赖关系。
- Pin 主要协调对象内容的使用和修改。
当然,Oracle 内部实现比这个复杂,不能简单把 Lock 理解成表锁,也不能把 Pin 理解成行锁。
这两种等待也不属于 enq: TX 那套事务行锁机制。
官方文档:Oracle Database 19c --- Descriptions of Wait Events
对应章节:
- C.3.89 --- library cache lock
- C.3.90 --- library cache pin
三、哪些操作容易引起 Library Cache 等待?
比较常见的场景是对象发生变更。
例如一个存储过程正在被应用调用,同时有人执行:
sql
ALTER PROCEDURE TEST_PROC COMPILE;
如果对象正在被其他会话使用,重新编译就可能需要等待相关 Library Cache 资源。反过来,如果某个会话正在编译或修改对象,其他需要访问该对象的会话也可能受到影响。
类似情况还可能出现在:
- 应用运行期间执行 DDL。
- 存储过程、Package 频繁失效或重新编译。
- SQL 硬解析或游标管理竞争。
- 大量会话并发访问相同的 Library Cache 资源。
不过,具体出现哪一种等待事件,要看当时争用的资源和操作,不能简单认为执行 DDL 就一定产生 library cache lock。
library cache load lock 主要出现在对象加载过程中。Oracle 为避免多个进程同时加载同一个对象,需要获取独占的 Load Lock。如果其他会话正在加载该对象,当前会话就可能等待。它和普通的 library cache lock 不是同一种机制。
四、Mutex 和 Lock、Pin 又有什么不同?
Oracle 还有一些比较常见的相关等待:
text
library cache: mutex X
cursor: pin S wait on X
这里涉及 Mutex(互斥机制)。
library cache: mutex X 表示会话在获取 Library Cache 相关结构的独占 Mutex 时发生等待,可能涉及对象查找、游标管理及并发解析等操作。实际排查时,还需要结合等待参数和 SQL 解析情况判断具体争用点。
cursor: pin S wait on X 表示当前会话需要获取游标的共享 Mutex Pin,但其他会话正以独占模式持有该资源,因此只能等待。这个事件通常需要结合 SQL 解析、游标共享和并发访问情况进一步分析。
因此,虽然它们都可能出现在 SQL 解析或执行过程中,但不能把 Mutex 等待和 library cache lock、library cache pin 混为一谈。
实际排查时,如果是 Mutex 类等待,我会更关注 SQL 解析频率、游标共享、子游标数量以及是否存在大量并发解析。如果是 Lock 或 Pin 等待,则需要进一步检查对象变更、失效、重新编译及持有相关资源的会话。
五、生产环境怎么查?
下面整理几条 Oracle 19c 常用的查询语句,先从实时会话入手。
1. 查看正在发生的 Library Cache 等待
sql
SELECT inst_id,
sid,
serial#,
username,
event,
state,
ROUND(wait_time_micro / 1000000, 3) AS wait_sec,
sql_id,
blocking_instance,
blocking_session,
module,
program
FROM gv$session
WHERE state = 'WAITING'
AND (
event LIKE 'library cache%'
OR event LIKE 'cursor: pin%'
)
ORDER BY wait_time_micro DESC;
先查看当前有哪些会话正在等待 Library Cache 相关资源,以及等待已经持续了多久。
如果多个会话集中在同一个等待事件或 SQL_ID 上,可以进一步检查等待参数和阻塞关系。BLOCKING_SESSION 可以作为参考,但并不是所有 Library Cache 等待都能直接通过它找到持有者。
2. 查看等待参数
sql
SELECT inst_id,
sid,
event,
p1text,
p1,
p1raw,
p2text,
p2,
p2raw,
p3text,
p3,
sql_id
FROM gv$session
WHERE state = 'WAITING'
AND event IN (
'library cache lock',
'library cache pin',
'library cache load lock'
);
对于 library cache lock 和 library cache pin,P1 通常是对象的 Handle Address。这个地址非常重要,因为后面可以尝试通过它关联到具体 Library Cache 对象。
但需要注意,不同等待事件的 P1、P2、P3 含义并不相同,不能把 Mutex 类等待的参数也按照相同方式解释。
3. 根据 Handle 定位对象和相关会话
Oracle 的 V$DB_OBJECT_CACHE 可以查看 Library Cache 中的对象信息:
sql
SELECT s.inst_id,
s.sid,
s.serial#,
s.event,
s.sql_id,
RAWTOHEX(s.p1raw) AS wait_handle,
o.owner,
o.name,
o.type,
o.namespace
FROM gv$session s
LEFT JOIN gv$db_object_cache o
ON o.inst_id = s.inst_id
AND o.addr = s.p1raw
WHERE s.state = 'WAITING'
AND s.event IN (
'library cache lock',
'library cache pin'
)
ORDER BY s.inst_id, s.sid;
这条 SQL 通过等待会话的 P1RAW 与 GV$DB_OBJECT_CACHE.ADDR 关联,尝试找到发生等待的 Library Cache 对象。对于 RAC,需要通过 INST_ID 确保关联的是同一个实例的数据。
sql
SELECT l.inst_id,
l.type,
RAWTOHEX(l.object_handle) AS object_handle,
RAWTOHEX(l.holding_session) AS holding_session,
RAWTOHEX(l.holding_user_session) AS holding_user_session,
s.sid,
s.serial#,
s.username,
s.status,
s.sql_id,
s.event,
l.mode_held,
l.mode_requested
FROM gv$libcache_locks l
LEFT JOIN gv$session s
ON s.inst_id = l.inst_id
AND s.saddr = l.holding_user_session
WHERE l.inst_id = :inst_id
AND l.object_handle = HEXTORAW(:handle_hex)
ORDER BY l.type,
l.mode_requested DESC,
l.mode_held DESC;
这条 SQL 根据对象 Handle 查询相关的 Library Cache Lock/Pin,查看资源的持有模式和请求模式,并尝试关联到具体会话。
HOLDING_SESSION 和 HOLDING_USER_SESSION 都是内部地址,不是 SID。
这里使用 HOLDING_USER_SESSION 与 GV$SESSION.SADDR 关联,但并不保证所有记录都能找到对应会话。如果关联不到,还需要结合内部会话地址和其他诊断信息进一步分析。
另外,RAC 中这些内存地址属于具体实例,不能拿实例 1 的地址去实例 2 中查。
4. 检查对象是否频繁失效
sql
SELECT owner,
name,
type,
loads,
executions,
invalidations
FROM v$db_object_cache
WHERE invalidations > 0
ORDER BY invalidations DESC
FETCH FIRST 30 ROWS ONLY;
如果某些对象存在较多 Invalidations,可以进一步检查近期是否发生过 DDL、对象重新编译或依赖对象变更。但 INVALIDATIONS 是累计计数,不能只凭数值高就判断它是当前等待的原因。
5. 查看解析情况
sql
SELECT name,
value
FROM v$sysstat
WHERE name IN (
'parse count (total)',
'parse count (hard)',
'execute count'
)
ORDER BY name;
这几个指标也是累计值,最好结合 AWR 的快照增量,判断异常时段解析次数是否明显增加。
如果出现大量 library cache: mutex X 或 cursor: pin S wait on X,可以进一步检查 V$SQL 中的版本数、解析调用次数和 SQL 共享情况。
六、如果是历史问题怎么办?
实时查询有一个明显的问题:等 DBA 开始排查时,等待可能已经消失了。
这时候就需要使用 ASH。
例如检查某个时间段内的 Library Cache 相关等待:
sql
SELECT instance_number,
sample_time,
session_id,
session_serial#,
sql_id,
event,
blocking_inst_id,
blocking_session,
p1text,
p1,
p2,
p3
FROM dba_hist_active_sess_history
WHERE sample_time >=
TO_TIMESTAMP(:begin_time, 'YYYY-MM-DD HH24:MI:SS')
AND sample_time <
TO_TIMESTAMP(:end_time, 'YYYY-MM-DD HH24:MI:SS')
AND session_state = 'WAITING'
AND (
event LIKE 'library cache%'
OR event LIKE 'cursor: pin%'
)
ORDER BY sample_time, instance_number;
这条 SQL 用于查看指定时间段内的历史 ASH 记录,包括等待事件、SQL_ID、会话信息、等待参数以及可能存在的阻塞会话。
需要注意,ASH 采用采样方式记录活跃会话,并不会保存每一次等待。DBA_HIST_ACTIVE_SESS_HISTORY 中的数据也可能经过进一步抽样,因此短时间内发生的等待不一定能查到。
如果发现某个时间段内有大量会话集中等待同一个事件,可以继续结合 SQL_ID、等待参数、阻塞会话和业务操作时间,进一步缩小排查范围。
七、排查时还有几个容易踩的坑
第一个是看到 Library Cache 等待就增加 Shared Pool。
Shared Pool 空间不足确实可能带来解析和缓存管理问题,但 Library Cache 等待也可能是对象变更、依赖关系或游标并发竞争造成的。
没有证据之前,直接增加 shared_pool_size 不一定有效。
第二个是发现等待会话后就准备 Kill Session。
Library Cache 等待的持有者不一定能通过 GV$SESSION.BLOCKING_SESSION 直接找到。即使找到相关会话,也需要先确认它正在执行什么操作。
如果正在进行对象编译、DDL 或重要业务操作,贸然终止可能带来额外影响。
第三个是直接执行 ALTER SYSTEM FLUSH SHARED_POOL。
这类操作可能导致大量游标失效和后续硬解析,生产环境不应该把它作为常规处理手段。
最后
Library Cache 相关等待比普通 I/O 等待复杂一些,主要是因为它涉及对象依赖、解析、编译以及多个会话之间的并发访问。
我觉得排查时可以先把事件分清楚:
library cache lock、library cache pin重点关注对象访问和变更;library cache: mutex X、cursor: pin S wait on X则需要更多关注游标和解析相关的并发竞争。
后面再通过等待参数、对象 Handle、SQL_ID 和 ASH 逐步定位。
至于具体是什么原因,还是要结合现场数据判断,不能只根据等待事件名称下结论。