重庆思庄Oracle、KingBase、PostgreSQL、Redhat认证学习论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 158|回复: 0
打印 上一主题 下一主题

[Oracle] Oracle Library Cache Lock / Mutex X 等待事件排障

[复制链接]
跳转到指定楼层
楼主
发表于 2026-5-10 21:18:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、背景知识
1.1 Mutex 与 Library Cache Lock 概述
Mutex 是从 Oracle 11g 开始引入的轻量级锁,用于替代传统的 latch,具备更轻量、更细粒度的特点:

锁类型        模式        说明
mutex X        eXclusive(独占)        排他访问,阻塞其他所有模式
mutex S        Share(共享)        共享访问,允许并发读
Library cache lock / mutex 用于保护共享池中的对象。在 Oracle 12c 及更高版本中,进一步拆分为三个独立的等待事件:

等待事件        保护对象        出现版本
library cache: mutex X        保护 handle        12c+
library cache: bucket mutex X        保护 library cache 中的 hash buckets        12c+
library cache: dependency mutex X        保护依赖关系        12c+
版本差异说明:

Oracle 11g:上述三种 mutex X 统一记录为 library cache: mutex X,不区分保护对象
Oracle 12c 及以上:拆分为三个独立等待事件,便于精确定位争用类型
Oracle 19c:AWR 报告新增 Mutex Sleep Summary 部分,可直接查看 mutex 等待的代码位置
版本适配建议: 生产环境建议使用 Oracle 19c 及以上版本,可获得更细粒度的 mutex 等待诊断信息。

1.2 通俗理解:Library Cache 是什么
Library Cache 就是 Oracle 内存里的一块**“共享书架”**——所有 SQL 语句、存储过程、表结构定义都放在这里,所有会话共用。

Library Cache Lock / Mutex 就是这个书架上的**“占位规则”**,防止大家同时改同一个东西导致数据错乱。

图书馆模型:

Library Cache = 公共书架,所有人共享
读一本书(执行 SELECT)→ 拿个 S 锁(共享),别人也能同时读
改书架上的书(ALTER TABLE、DROP TABLE、编译存储过程)→ 拿个 X 锁(独占),其他人全部等着
时间        会话 A        会话 B
10:00        ALTER TABLE emp ADD ...(拿 X 锁)       
10:01        还在改…        SELECT * FROM emp(等 S 锁,被阻塞)
10:02        还在改…        更多 SELECT 排队等
10:05        ALTER 完成,释放 X 锁        全部放行
1.3 与数据库锁(DML Lock)的区别
这是最容易混淆的点:数据库锁管的是数据,Library Cache Lock 管的是元数据。

公司文件柜模型:

数据库锁(行锁/表锁)= 文件柜里文件内容的锁
Library Cache Lock = 文件柜门上目录卡片的锁
操作        需要什么锁        为什么
UPDATE emp SET sal=999        行锁(DML Lock)        改的是数据行本身
SELECT * FROM emp        不加行锁(读一致)        只是读数据
ALTER TABLE emp ADD col        Library Cache Lock (X)        改的是表结构定义(目录卡片)
SELECT * FROM emp(表刚被 ALTER)        Library Cache Lock (S)        要先确认表结构没变,才能读数据
关键区别:

维度        数据库锁(DML Lock)        Library Cache Lock / Mutex
保护对象        数据行、表的数据内容        共享池中的对象定义(SQL文本、表结构、执行计划)
典型锁        行锁(TX)、表锁(TM)        library cache lock、library cache: mutex X
产生场景        INSERT/UPDATE/DELETE 互相冲突        DDL 操作、硬解析、编译存储过程
查看方式        V$LOCK、GV$LOCK        X$KGLLK、Systemstate dump
影响范围        只影响操作同一行/表数据的人        影响所有引用该对象的 SQL 执行
为什么 SELECT 也会等 Library Cache Lock? 因为执行任何 SQL 之前,Oracle 必须先去共享池里查这个表还在不在、结构变没变——这就是读"目录卡片"的过程。如果有人正在改"卡片"(ALTER TABLE),所有人连查数据的机会都没有,得先等卡片更新完。

一句话:数据库锁是"别动我正在改的数据",Library Cache Lock 是"别动我正在改的定义"。 前者管数据一致性,后者管共享池对象一致性。

1.4 Mutex 更轻,但"爆炸半径"更大
Mutex 本身确实比传统锁更轻量级,这正是 11g 引入它替代 Latch 的原因:

锁类型        占用内存        获取方式        适用场景
Latch        ~100+ 字节        自旋 + 退避,需 OS 上下文切换        10g 及之前保护共享池
Mutex        ~几十字节        纯自旋,用户态完成        11g+ 替代 latch 保护共享池
DML Lock        更大        队列等待,涉及 ENQUEUE 机制        保护数据行/表
Mutex 不需要 OS 层面的上下文切换,纯内存操作,单个开销确实更小。

但这里有个反直觉的关键点——Mutex 虽然轻,但 Library Cache Lock (X) 的阻塞范围远大于行锁:

锁        阻塞范围        举例
行锁(TX)        只堵改同一行的人        10 个人改不同行,互不影响
表锁(TM)        堵对同一表做冲突操作的人        互斥的 DML 之间
Library Cache Lock (X)        堵所有引用该对象的会话        一个人 ALTER TABLE,全库所有查这张表的 SELECT 全等
轻量不等于影响小——就像一把小锁锁住了大门,锁本身很轻,但所有人都进不去。

1.5 何时需要关注
library cache lock 和 library cache: mutex X 均属于 Concurrency(并发) 类型的等待事件。当以下条件满足时,应进行排查:

该等待事件的 %DB Time 占比高于 10%
成为 Top 1 或 Top 2 等待事件
需要准确定位阻塞源(X 锁持有方),最大限度避免该类等待事件反复出现
二、问题现象确认
2.1 检查 DB Time 确认负载异常时段
小贴士: 执行前需确认 AWR 快照已开启,且问题时段有快照覆盖。可通过 SELECT snap_id, begin_interval_time FROM dba_hist_snapshot ORDER BY snap_id 确认。

col awrrpt_name for a56
col dtpc for 999999.99
col date_time for a18
WITH timemodel_tmp
     AS (SELECT sn.begin_interval_time, sn.end_interval_time, ss.stat_name, cp.pdb_name,
                os.value cpu_cnt, ss.VALUE e_value, ss.snap_id e_snap_id, i.instance_number,
                LAG (ss.VALUE, 1) OVER (partition by ss.instance_number, ss.stat_name ORDER BY ss.snap_id) b_value,
                LAG (ss.snap_id, 1) OVER (partition by ss.instance_number, ss.stat_name ORDER BY ss.snap_id) b_snap_id
           FROM dba_hist_con_sys_time_model ss, dba_hist_snapshot sn, DBA_HIST_OSSTAT os,
                (select dbid, instance_number, row_number()over(partition by dbid, instance_number order by STARTUP_TIME desc) rn from DBA_HIST_DATABASE_INSTANCE) i
                 , cdb_pdbs cp
           WHERE sn.begin_interval_time between to_date('&begin_time','yyyy-mm-dd hh24:mi') and to_date('&end_time','yyyy-mm-dd hh24:mi')
                AND ss.snap_id = sn.snap_id  AND ss.dbid = sn.dbid
                AND ss.snap_id = os.snap_id  AND ss.dbid = os.dbid
                AND ss.instance_number = sn.instance_number AND ss.instance_number = os.instance_number
                AND ss.dbid = (SELECT dbid FROM v$database)
                AND ss.instance_number = i.instance_number AND ss.dbid = i.dbid AND i.rn=1
                AND ss.stat_name in ( 'DB time', 'DB CPU') AND os.stat_name = 'NUM_CPUS'),
timemodel as (select begin_interval_time, end_interval_time, pdb_name, cpu_cnt, instance_number,
                max(case stat_name when 'DB time' then e_value end) dt_e_value,     max(case stat_name when 'DB CPU' then e_value end) ct_e_value,
                max(case stat_name when 'DB time' then e_snap_id end) e_snap_id,
                max(case stat_name when 'DB time' then b_value end) dt_b_value,     max(case stat_name when 'DB CPU' then b_value end) ct_b_value,
                max(case stat_name when 'DB time' then b_snap_id end) b_snap_id
             from timemodel_tmp
             group by begin_interval_time, end_interval_time, pdb_name, cpu_cnt, instance_number),
candi_tmp as (
  SELECT TO_CHAR (BEGIN_INTERVAL_TIME, 'mm-dd hh24:mi')
         || TO_CHAR (END_INTERVAL_TIME, '~hh24:mi')  date_time,
         ROUND ((dt_e_value - NVL (dt_b_value, 0))/1000000
            / ((cast (end_interval_time as date) - cast(begin_interval_time as date))*24*60*60), 2)  db_times,
         ROUND ((dt_e_value - NVL (dt_b_value, 0))/1000000
            / ((cast (end_interval_time as date) - cast(begin_interval_time as date))*24*60*60) / cpu_cnt, 2)  db_times_per_cpu,
         ROUND ((ct_e_value - NVL (ct_b_value, 0))/1000000
            / ((cast (end_interval_time as date) - cast(begin_interval_time as date))*24*60*60), 2)  cpu_times,
         ROUND ((ct_e_value - NVL (ct_b_value, 0))/1000000
            / ((cast (end_interval_time as date) - cast(begin_interval_time as date))*24*60*60) / cpu_cnt, 2)  cpu_times_per_cpu,
         b_snap_id, e_snap_id, BEGIN_INTERVAL_TIME, END_INTERVAL_TIME, pdb_name, instance_number
    FROM timemodel
   WHERE (dt_e_value - NVL (dt_b_value, 0)) > 0 AND NVL (dt_b_value, 0) > 0),
candi as (select t.*, row_number()over(partition by instance_number order by BEGIN_INTERVAL_TIME desc) rn
         from candi_tmp t)
select date_time, instance_number inst, db_times dt, db_times_per_cpu dtpc, cpu_times ct, cpu_times_per_cpu ctpc, b_snap_id, e_snap_id,
       'awr_'||pdb_name||instance_number||'_'||TO_CHAR (BEGIN_INTERVAL_TIME, 'yyyy-mm-dd-hh24mi')||TO_CHAR (END_INTERVAL_TIME, '~hh24mi')||'_'||to_char(db_times)||'_'||to_char(cpu_times)||'.html' awrrpt_name
from candi c
order by BEGIN_INTERVAL_TIME, instance_number;
注意: 该查询使用了 cdb_pdbs 视图,仅适用于 Oracle 12c 及以上版本的 CDB 架构。非 CDB 环境需移除 cdb_pdbs cp 相关的 JOIN 和 pdb_name 字段。

2.2 确认问题时段等待事件及对应 SQL
ASH 区间等待排名
col event for a35
col MACHINE for a35
col program for a40
select *
  from (select /*+ parallel(a,4) */
         a.event, a.machine, a.program, a.sql_id, count(*) as ev_cnt
          from dba_hist_active_sess_history a
         where a.sample_time between
               to_date('&begin_time', 'yyyy-mm-dd hh24:mi') and
               to_date('&end_time', 'yyyy-mm-dd hh24:mi')
           and event != 'SQL*Net message from dblink'
         group by a.event, a.machine, a.program, a.sql_id
        having count(*) > 200)
order by ev_cnt desc, machine, sql_id;
小贴士: having count(*) > 200 是过滤低频事件的阈值,可根据实际情况调整。如果问题时段较短,建议降低阈值(如 > 50)以避免遗漏。

确认问题时段的等待事件
select a.event,
       count(*) cnt,
       round(avg(a.time_waited) / 1000, 0) avg_wait_ms,
       round(max(a.time_waited) / 1000, 0) max_wait_ms
  from gv$active_session_history a
where a.sample_time between
       to_date('&begin_time', 'yyyy-mm-dd hh24:mi') and
       to_date('&end_time', 'yyyy-mm-dd hh24:mi')
group by event
order by cnt desc;
注意: gv$active_session_history 仅保存在内存中,默认保留最近 1 小时数据。如果问题时段已过去,请使用 dba_hist_active_sess_history(基于 AWR 快照)。

等待事件对应 SQL、阻塞源会话查询
set lines 300 pages 999
col module for a20
col event for a25
col P1TEXT for a25
col P2TEXT for a25
col P3TEXT for a25
select INST_ID, sql_id, module, event, P1TEXT, P1, P2TEXT, P2, P3TEXT, P3, BLOCKING_INSTANCE, BLOCKING_SESSION, count(*) cnt
  from gv$active_session_history
where sample_time between
       to_date('&begin_time', 'yyyy-mm-dd hh24:mi') and
       to_date('&end_time', 'yyyy-mm-dd hh24:mi')
   and event = '&event'
group by INST_ID, sql_id, module, event, P1TEXT, P1, P2TEXT, P2, P3TEXT, P3, BLOCKING_INSTANCE, BLOCKING_SESSION
order by cnt desc;
P1/P2/P3 参数含义(library cache: mutex X):

参数        含义
P1        Mutex 针对的对象 hash value
P2        Mutex 的内部标识符
P3        Mutex 等待的位置代码
三、排查步骤:定位阻塞源会话
方法一:X$KGLLK 表查询(推荐,资源消耗最低)
小贴士: 这是四种方法中资源消耗最低的方式,优先使用。需以 SYS 用户执行。

XKGLLK 表包含所有会话的 library object locks(持有的和请求的),比 VLOCK 视图更完整。

步骤 1:查找等待会话的 SADDR
select sid, saddr from v$session where event = 'library cache lock';
步骤 2:查找等待会话请求的锁及对象
select kgllkhdl Handle, kgllkreq Request, kglnaobj Object
  from x$kgllk
where kgllkses = '&saddr'    -- 替换为步骤1查到的SADDR
   and kgllkreq > 0;
输出说明:

列名        含义
HANDLE        对象的 handle 地址,与 Systemstate 中的 handle address 对应
REQUEST        请求的锁模式(>0 表示正在请求,2=S,3=X)
Object        对象名称的前 80 个字符
步骤 3:查找阻塞源会话
select kgllkses saddr, kgllkhdl handle, kgllkmod mod, kglnaobj object
  from x$kgllk lock_a
where kgllkmod > 0
   and exists (select lock_b.kgllkhdl
          from x$kgllk lock_b
         where kgllkses = '&blocked_saddr'   -- 替换为被阻塞会话的SADDR
           and lock_a.kgllkhdl = lock_b.kgllkhdl
           and kgllkreq > 0);
kgllkmod 值含义: 1=NULL,2=S(共享),3=X(独占)

步骤 4:查询阻塞源会话详细信息
select sid, username, terminal, program
  from v$session
where saddr = '&blocking_saddr';   -- 替换为步骤3查到的阻塞源SADDR
步骤 5(可选):查找被该阻塞源阻塞的所有会话
select sid, username, terminal, program
  from v$session
where saddr in
       (select kgllkses
          from x$kgllk lock_a
         where kgllkreq > 0
           and exists (select lock_b.kgllkhdl
                  from x$kgllk lock_b
                 where kgllkses = '&blocking_saddr'   -- 阻塞源的SADDR
                   and lock_a.kgllkhdl = lock_b.kgllkhdl
                   and kgllkreq = 0));
方法二:DIA0 Trace 文件分析(适用于 RAC 和单机)
小贴士: 此方法无需手动执行 dump,直接读取 DIA0 进程自动生成的 Trace 文件,资源消耗较低。

步骤 1:定位 DIA0 Trace 文件
DIA0 进程的 Trace 文件位于 BACKGROUND_DUMP_DEST(11g)或 diag/rdbms/[db_name]/[instance]/trace(12c+)目录下,文件名格式为 [实例名]_dia0_[PID].trc。

-- 查找 DIA0 进程的 Trace 文件路径
select value from v$diag_info where name = 'Diag Trace';
步骤 2:在 Trace 文件中搜索阻塞链
搜索关键字:Chains most likely to have caused the hang

示例输出:

Chain 1 Signature: <='library cache lock'
如果存在多级阻塞,链条会显示完整的阻塞关系,最右侧是最外层等待会话。

步骤 3:定位阻塞源会话
继续向下阅读,找到 Chain 1 的详细信息:

Oracle session identified by:
    {
                instance: 2 ([实例名])
                   os id: [OS_PID]
              process id: 185, oracle@[实例名]
              session id: 1380          <===== 被阻塞的业务会话
        session serial #: 37293
    }
    is waiting for 'library cache lock' with wait info:
    {
                      p1: 'handle address'=0x700010eb4796968
                      p2: 'lock address'=0x70001039a764610
                      p3: '100*mode+namespace'=0x4b90d00010003
    }
    and is blocked by                   <===== 阻塞关系
=> Oracle session identified by:
    {
                instance: 2 ([实例名])
                   os id: [OS_PID]
              process id: 349, oracle@[实例名] (TNS V1-V3)
              session id: 1058          <===== 阻塞源会话
        session serial #: 18162
    }
步骤 4:获取阻塞源会话执行的 SQL
继续阅读直到:

HM: Short Stack of immediate waiter session ID 1380
HM: Short Stack of root session ID 1058
此处包含两个会话操作的 SQL,根据该 SQL 可判断阻塞源会话正在执行的操作类型。

常见阻塞场景: DDL 操作(ALTER TABLE、DROP、CREATE 等)是最常见的阻塞源,建议避开业务高峰期执行。

方法三:Systemstate Dump 分析(资源消耗较高)
⚠️ 警告: Systemstate dump 会消耗较多资源,建议仅在方法一和方法二无法定位问题时使用。切勿在业务高峰期执行。

步骤 1:生成 Systemstate Trace 文件
非 RAC 数据库:

$ sqlplus '/ as sysdba'
oradebug setmypid
oradebug unlimit
oradebug dump systemstate 266
-- 等待 90 秒
oradebug dump systemstate 266
-- 等待 90 秒
oradebug dump systemstate 266
quit
注意: 连续执行 3 次间隔 90 秒的 dump,便于对比确认阻塞是否持续存在。

RAC 数据库:

$ sqlplus '/ as sysdba'
oradebug setmypid
oradebug unlimit
oradebug setinst all
oradebug -g all hanganalyze 4
oradebug -g all dump systemstate 266
quit
版本差异说明:

Oracle 11g+:推荐 level 266 或 258(266 包含 short stack 信息)
Level 266 包含更完整的 mutex 信息和进程 short stack,是排障的标准级别
步骤 2:获取被阻塞会话 PID
select inst_id, pid from gv$process where addr in
  (select paddr from gv$session where event = '&event');
步骤 3:在 Trace 文件中查找阻塞信息
在生成的 Trace 文件中搜索被阻塞会话的 PID,在 PROCESS 部分搜索 waiting for:

PROCESS 20:
----------------------------------------
SO: 0x7d2bd820, type: 2, owner: (nil), flag: INIT/-/-/0x00
(process) Oracle pid=20, calls cur/top: 0x7d3d62d0/0x7d3d85dc, flag: (0) -
int error: 0, call error: 0, sess error: 0, txn error 0
O/S info: user: oracle, term: pts/7, ospid: 19759
OSD pid info: Unix process pid: 19759, image: [主机名] (TNS V1-V3)

(session) sid: 141 trans: (nil), creator: 0x7d2bd820, flag: (41) USR/- BSY/-/-/-/-/-
waiting for 'library cache lock' blocking sess=0x(nil) seq=36 wait_time=0
handle address=62d064dc, lock address=79f88a68, 100*mode+namespace=c9
步骤 4:根据 handle address 查找被锁定对象
根据 handle address 搜索 Trace 文件中同一地址的 LIBRARY OBJECT HANDLE 部分:

LIBRARY OBJECT LOCK: lock=79f88a68 handle=62d064dc request=S
call pin=(nil) session pin=(nil) hpc=0000 hlc=0000
LIBRARY OBJECT HANDLE: handle=62d064dc mtx=0x62d06590(0) cdp=0
name=[用户名].EMPLOYEES
步骤 5:查找锁持有方
在同一 Trace 文件中搜索 handle=62d064dc 的其他进程,找到 mode=X 的持有方:

PROCESS 18:
LIBRARY OBJECT LOCK: lock=75fe8f7c handle=62d064dc mode=X
LIBRARY OBJECT HANDLE: handle=62d064dc mtx=0x62d06590(0) cdp=0
name=[用户名].EMPLOYEES
PID 18 的进程对该对象持有排他锁(mode=X)。

步骤 6:根据 PID 查询阻塞源会话信息
alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';
set lines 1000
col machine for a25
col username for a10
col program for a18
col kill_spid for a30
col event for a40
col sql_id for a15
col wait_class for a15
select a.inst_id,
       a.sid,
       a.machine,
       a.username,
       a.program,
       a.status,
       a.logon_time,
       a.sql_exec_start,
       a.prev_sql_id,
       a.sql_id,
       a.state,
       (case
         when a.state = 'WAITING' then
          a.seconds_in_wait
         when a.state = 'WAITED KNOWN TIME' then
          a.wait_time
         else
          NULL
       end) wait_time_sec,
       a.blocking_instance,
       a.blocking_session,
       a.wait_class,
       a.event,
       a.last_call_et,
       'kill -9 ' || spid as kill_spid
  from gv$session a, gv$process b
where a.inst_id = b.inst_id
   and a.paddr = b.addr
   and b.pid = &pid;
注意: kill -9 为 Linux 命令,Windows 环境下应使用 orakill [ORACLE_SID] [SPID]。

方法四:Errorstack 获取进程信息(资源消耗中等)
⚠️ 警告: Errorstack 比Systemstate磁盘占用小,但仍会消耗资源,建议在确认阻塞后使用。

步骤 1:获取阻塞会话的 OS PID
select spid from v$process where addr =
  (select paddr from v$session where sid = &blocking_sid);
步骤 2:生成 Errorstack
$ sqlplus '/ as sysdba'
oradebug setospid &spid        -- 替换为步骤1查到的OS PID
oradebug dump errorstack 3
-- 等待 1 分钟
oradebug dump errorstack 3
-- 等待 1 分钟
oradebug dump errorstack 3
exit
步骤 3:分析 Trace 文件
生成的 Trace 文件的 stack 可用于匹配已知问题。系统状态和 errorstacks 不容易理解,可能需要开 SR 进行 Trace 分析。

四、library cache: mutex X 热对象检查
4.1 获取 Top 10 mutex X 等待的 P1 参数值
select *
  from (select p1,
               sql_id,
               count(*),
               (ratio_to_report(count(*)) over()) * 100 pct
          from dba_hist_active_sess_history
         where event = 'library cache: mutex X'
           and snap_id between &begin_snapid and &end_snapid
           and dbid = &dbid
         group by p1, sql_id
         order by count(*) desc)
where rownum <= 10;
4.2 根据 P1 值检查 Mutex 针对的对象
判断规则(参考 MOS Doc ID 1357946.1):

如果相同的 SQL_ID 在 Top 10 中以不同的 P1 值出现 → 问题很可能与该 SQL 语句本身相关
如果 SQL_ID 和 P1 是唯一的 → 很可能是一个热对象
select KGLNAOBJ, KGLNAOWN, KGLHDNSP, KGLOBTYP
  from x$kglob
where KGLNAHSH = &P1;
4.3 热对象相关 Bug 检查
如果存在热对象,检查以下 Bug:

Bug 号        说明        影响版本
Bug 9239863        Excessive “library cache:mutex X” contention on hot objects        11g
进阶技巧: 如果没有明确的热对象,但 mutex waits 整体较高,应进一步分析 AWR 报告中的 Load Profile 部分,关注 Hard parses 指标。

五、AWR 和 ADDM 报告分析
5.1 收集报告
-- 收集问题时段的 AWR 报告
SQL> @$ORACLE_HOME/rdbms/admin/awrrpt.sql

-- 收集问题时段的 ADDM 报告
SQL> @$ORACLE_HOME/rdbms/admin/addmrpt.sql
小贴士: 同时收集问题时段和正常时段的 AWR/ADDM 报告,便于对比分析负载、参数变更等差异。

5.2 AWR 报告关键检查项
根据 MOS Doc ID 1357946.1 和 Doc ID 1377998.1,重点检查以下内容:

检查项 1:Top 等待事件
如果 Top 等待事件为 library cache lock 和 library cache: mutex X,说明会话正在等待获取一个或多个游标的独占模式 library cache mutex。

检查项 2:Load Profile → Hard Parses
硬解析可能导致 SQL Area 的重新载入。如果硬解析较高,同时检查 Library Cache Activity 中是否存在高重载 SQL AREA。

高重载的两种场景:

场景        原因        处理方式
游标共享效率低        不可重用的游标填满 library cache,可重用游标被清除        优化应用,使用绑定变量,减少硬解析
共享池过小        可共享的 SQL 语句老化后硬解析增高        考虑增大 shared_pool_size / sga_target
判断依据: 如果共享效率低,即使增大共享池也只是延缓问题;如果共享效率高但共享池不足,增大共享池可缓解。

检查项 3:SQL Statistics → SQL ordered by Parse Calls
指标        理想值        说明
Parse Calls / Executions        接近 1:1 表示游标未被有效复用        游标应保持打开状态以重新执行 SQL
如果 Parse Calls 与 Executions 接近 1:1,说明应用未有效使用游标缓存。建议与开发团队沟通,保持游标打开。

检查项 4:SQL Statistics → SQL ordered by Version Count
高版本计数的 SQL 在解析时需要检查所有版本是否匹配,导致 mutex 被持有更长时间,增加争用概率。

进阶技巧: 使用以下查询快速定位高版本计数 SQL:

select sql_id, version_count, sql_text
  from v$sqlarea
where version_count > 100
order by version_count desc;
检查项 5:Mutex Sleep Summary
在 Oracle 较高版本(19c+)的 AWR 报告中,Mutex Sleep Summary 部分显示等待 mutex 的 library cache 代码位置(内部标识符),可用于检索对应的 Bug。

检查项 6:CURSOR_SHARING 参数
对于 Oracle 11g,确认 CURSOR_SHARING 参数值不是 SIMILAR:

show parameter cursor_sharing
版本差异说明:

Oracle 11g:CURSOR_SHARING=SIMILAR 已弃用,可能引起 mutex X 等待,应设置为 EXACT 或 FORCE
Oracle 12c+:SIMILAR 值不再支持,强制转换为 EXACT 或 FORCE
参考: MOS Doc ID 1169067.1

六、解决方案与预防措施
6.1 短期处理
操作        说明        风险等级
Kill 阻塞源会话        终止持有 X 锁的会话,释放阻塞        ⚠️ 中(需确认业务影响)
等待阻塞操作完成        如果是 DDL 操作,可等待其自然完成        低
调整执行窗口        将 DDL 操作调度到业务低峰期        低
6.2 长期预防
优化方向        具体措施
减少硬解析        使用绑定变量、设置合理的 CURSOR_SHARING 参数
避免高版本计数        避免不必要的字面量 SQL、检查 SQL ordered by Version Count
共享池合理配置        确保 shared_pool 足够大,避免游标频繁老化
DDL 操作管控        避开业务高峰期执行 ALTER/DROP/CREATE 等操作
定期巡检        监控 %DB Time 中 library cache 等待事件占比
应用层优化        保持游标打开,减少 Parse Calls
七、总结 & 注意事项
核心要点
优先使用 X$KGLLK 方法定位阻塞源,资源消耗最低,操作最便捷
library cache lock / mutex X 占 DB Time 超过 10% 即需关注
12c+ 版本中等待事件细化为三个,便于精确定位争用类型
DDL 操作是常见阻塞源,务必避开业务高峰期执行
高硬解析和高版本计数是 mutex 争用的两大主因
注意事项
Systemstate dump 和 Errorstack 会消耗较多资源,仅在必要时使用,且避开业务高峰
X$KGLLK 表只能通过 SYS/INTERNAL 账户访问
gv$active_session_history 仅保留最近约 1 小时数据,历史数据需查询 dba_hist_active_sess_history
Kill 会话前务必确认对业务的影响,Windows 环境使用 orakill 而非 kill -9
CURSOR_SHARING=SIMILAR 在 11g 中已弃用,务必修改为 EXACT 或 FORCE
参考文档
文档编号        标题
Doc ID 122793.1        How to Find which Session is Holding a Particular Library Cache Lock
Doc ID 1357946.1        Troubleshooting ‘library cache: mutex X’ Waits
Doc ID 1377998.1        Troubleshooting: Waits for Mutex Type Events
Doc ID 2051456.1        Troubleshooting Databases Hang Due to Heavy Contention for ‘library cache: mutex X’ Waits (Oracle 11.2 and Later)
Doc ID 727400.1        WAITEVENT: “library cache: mutex X”
Doc ID 2533901.1        ‘Library Cache Lock’, ‘Library cache: mutex X’ 以及因为 ‘Error=936’ 导致的很高的解析失败

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 支持支持 反对反对
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|手机版|小黑屋|重庆思庄Oracle、Redhat认证学习论坛 ( 渝ICP备12004239号-4 )

GMT+8, 2026-5-24 07:49 , Processed in 0.322635 second(s), 21 queries .

重庆思庄学习中心论坛-重庆思庄科技有限公司论坛

© 2001-2020

快速回复 返回顶部 返回列表