Oracle 性能诊断的核心思想叫 OWI(Oracle Wait Interface) ,一句话:会话要么在 CPU 上执行,要么在等待。把所有会话"等什么、等多久"统计起来,瓶颈就藏不住。本篇先给速查表和诊断 SQL 模板,再用一个"节点宕机"的真实案例,走一遍从系统日志到 MOS BUG 的完整破案流程。
一、OWI 的三个数据层级
| 层级 | 视图/工具 | 保留 | 用途 |
|---|---|---|---|
| 实时 | v$session_wait / v$session |
当前瞬间 | "现在卡住了" |
| 近期 | v$active_session_history(内存) |
约数小时 | "刚过去的一小时" |
| 历史 | dba_hist_active_sess_history(AWR 底层) |
随 AWR 保留(默认 8 天) | 任意历史时段回溯 |
诊断习惯 :能实时看就用 v$session,需要历史区间就用 dba_hist_*,两者结构几乎一致。
sql
-- 当前谁在等什么(实时三板斧)
SELECT sid, serial#, username, event, wait_class, sql_id, blocking_session
FROM v$session
WHERE state = 'WAITING' AND wait_class != 'Idle'
ORDER BY seconds_in_wait DESC;
-- 历史区间等待事件分布(换 sample_time 即可)
SELECT instance_number, event, COUNT(*)
FROM dba_hist_active_sess_history
WHERE sample_time BETWEEN TO_DATE('2026-08-14 12:00','yyyy-mm-dd hh24:mi')
AND TO_DATE('2026-08-14 12:16','yyyy-mm-dd hh24:mi')
GROUP BY instance_number, event
HAVING COUNT(*) > 50
ORDER BY 3 DESC;
ASH 里
COUNT(*)的含义:采样次数 ≈ 该等待消耗的秒数(1 秒采一次)。2434 次采样 ≈ 约 40 分钟的 DB Time 耗在这个事件上。
二、常见等待事件速查表(按 wait_class 分组)
Application(应用层)
| 事件 | 含义 | 动作 |
|---|---|---|
enq: TX - row lock contention |
行锁争用 | \[09-锁阻塞与死锁排查] |
enq: TM - contention |
表级锁:DDL 撞 DML / 外键无索引 | 找 DDL 来源,外键建索引 |
Concurrency(并发)
| 事件 | 含义 | 动作 |
|---|---|---|
buffer busy waits |
热块争用(段头/数据块) | 反向键索引、分区打散、调 initrans |
latch: shared pool / library cache: mutex X |
硬解析过多 | 绑定变量(\[10-慢SQL分析路径]) |
cursor: mutex X / S |
游标互斥:解析/子游标过多 | 见本篇案例 |
row cache lock |
数据字典缓存争用(常见于密集 DDL) | 查 DDL 来源 |
System I/O / Commit
| 事件 | 含义 | 动作 |
|---|---|---|
log file sync |
commit 等 LGWR 刷盘 | \[08-log-file-sync提交慢专题] |
log file switch (checkpoint incomplete) |
redo 太小/太少,切换卡住 | 加大 redo、加组 |
db file scattered read |
全表扫多块读 | SQL 加索引/改写 |
db file sequential read |
索引单块读 | 看回表量、存储延迟 |
db file parallel write/read |
DBWR/IO 层 | 查存储与 OSW |
Cluster(RAC 专属)
| 事件 | 含义 | 动作 |
|---|---|---|
gc buffer busy acquire/release |
跨节点块争用 | 应用亲和性、反向键索引 |
gc cr block 2-way/3-way |
跨节点构造 CR 块 | 查私网延迟 |
可以忽略的 Idle 类
SQL*Net message from client(等应用发指令)、ges generic event(RAC 空闲)等------先过滤 wait_class='Idle' 再分析,否则榜首永远是空闲等待。
三、真实案例:16 分钟内存耗尽导致节点宕机
背景:一套 12cR2 RAC 生产库的真实故障。
3.1 案情
2024-06-17 12:30,12cR2 两节点 RAC 的节点二宕机,宕机前观察到内存耗尽。
3.2 第一步:OSW 圈定时间窗
OSW(OS Watcher)的 top 采样显示:
12:00 free 内存 52,011,212 KB
12:10 free 内存 15,533,284 KB ← 10 分钟掉 36 GB
12:16 free 内存 1,220,792 KB ← 耗尽边缘
12:18 之后无采样(主机失联/宕机)
结论:故障酝酿期 12:00--12:16,16 分钟。
3.3 第二步:alert.log 定性
log
ORA-16198: LGWR received timedout error from KSR ← 先有网络层异常
ORA-00603: ORACLE server session terminated by fatal error
ORA-27300: OS system dependent operation:sendmsg failed with status: 105
ORA-27301: OS failure message: No buffer space available ← 内核无内存分配 socket
No buffer space available(errno 105 ENOBUFS):内存耗尽连 socket 缓冲区都分配不出,网络功能连锁崩溃------与 OSW 的内存曲线互相印证。命中 MOS Doc ID 2041723.1。
3.4 第三步:ASH 找元凶事件
对 12:00--12:16 窗口统计:
INSTANCE EVENT COUNT(*)
2 cursor: mutex X 2434 ← 一枝独秀
1/2 ges generic event 384/360 ← 空闲,忽略
1/2 log file switch (checkpoint incomplete) 181/89 ← 次要:redo 偏小
3.5 第四步:锁定 SQL 并深挖游标
sql
-- 哪条 SQL 贡献了 cursor: mutex X
SELECT sql_id, COUNT(*) FROM dba_hist_active_sess_history
WHERE event IN ('cursor: mutex S','cursor: mutex X')
GROUP BY sql_id HAVING COUNT(*) > 10;
-- 子游标数量核实(version count 报告)
SELECT * FROM TABLE(version_rpt('7gpjgqbvfghgv'));
-- 关键输出:
-- Sharable_Mem: 180,850,612 bytes ← 单条 SQL 吃掉 172 MB 共享内存
-- Total Versions: 2594 ← 2594 个子游标!
-- BIND_EQUIV_FAILURE : 2589 ← 绑定变量等价性检查失败
-- 不共享原因核实
SELECT child_number, BIND_EQUIV_FAILURE
FROM v$sql_shared_cursor WHERE sql_id='7gpjgqbvfghgv';
破案 :SQL 带绑定变量,但自适应游标共享(ACS)机制与该版本优化器 BUG 相互作用,BIND_EQUIV_FAILURE 导致每执行几次就生成新子游标,16 分钟膨胀到 2594 个、共享内存 172 MB+,配合解析风暴把内存打爆。命中 BUG 28794230(MOS Doc ID 2539161.1)。
3.6 第五步:修复
临时缓解(无法停机时):
sql
ALTER SYSTEM SET "_optimizer_use_feedback"=false SCOPE=SPFILE;
ALTER SYSTEM SET "_optimizer_adaptive_cursor_sharing"=false SCOPE=SPFILE;
ALTER SYSTEM SET "_optimizer_extended_cursor_sharing_rel"=none SCOPE=SPFILE;
根本修复:打补丁 28794230(支持滚动打补丁,RAC 可逐节点操作)。
系统层加固 (本案例主机直接宕机的教训):设置 vm.min_free_kbytes 为物理内存的 0.4%(本机 263 GB → 约 1,054,457 KB),给内核保留底仓,避免内存耗尽后整机失联。
3.7 案例复盘:方法论的价值
OSW 圈时间窗 → alert.log 定性(内存/网络) → ASH 定位事件(cursor: mutex X)
→ 事件关联 SQL → version_rpt/v$sql_shared_cursor 找根因(子游标爆炸)
→ 比对 MOS 命中 BUG → 缓解 + 补丁 + 内核参数加固
五层递进,每层都有数据支撑------这就是 \[01-故障排查方法论与工具箱] 六步框架的完整落地。
四、日常监听等待事件的 SQL(巡检可用)
sql
-- 实例启动以来的 Top 等待
SELECT event, total_waits, time_waited/100 sec_waited
FROM v$system_event
WHERE wait_class != 'Idle'
ORDER BY time_waited DESC FETCH FIRST 10 ROWS ONLY;
-- 当前阻塞链速览
SELECT sid, blocking_session, wait_class, event, sql_id
FROM v$session
WHERE blocking_session IS NOT NULL;
五、小结
- OWI:会话非在 CPU 即在等待,统计等待即定位瓶颈;
- 三层数据:v 实时 → vactive_session_history 近期 → dba_hist_* 历史;
- 分析前先滤掉 Idle 类;
cursor: mutex X案例展示标准破案链:OSW → alert → ASH → SQL → 游标 → BUG;- 系统层加固(vm.min_free_kbytes)与数据库修复同等重要。
下一篇:\[08-log-file-sync提交慢专题] ------ 把第一个高频等待事件拆到齿轮级。