liuziyi

liuziyi

别背 V$ 视图了:一张"家族地图" + 一套可照抄的排障 SOP

从一个几乎人人踩过的坑说起

刚接手 Oracle 排障时,最容易被带偏的一件事,就是试图把《Database Reference》里的动态性能视图全背下来。几百个 V$ 开头的东西,翻到第三章基本就放弃了,结果真出问题时,手还是只会敲一句 SELECT * FROM v$session,然后盯着满屏字段发呆。

问题不在记性,在方法。视图不是靠背字段记住的,是靠"它回答什么问题"记住的。这篇不止给你地图,还给你一套可以照着敲的排障流程(SOP):先判断该查哪一族,再用现成的 SQL 收敛,最后落到具体的段或 SQL 上收口。字段等到用时再翻文档,这才是能长期受用的姿势。

一句话说清这套地图

Oracle 的动态性能视图,本质上不是几百个孤立对象,而是几个模板的排列组合:三个设计原则决定骨架,五大家族决定职责分工,一条三步下钻法把它们串成排障流水线。记住这三层,你就能在需要时迅速定位"该查哪个视图",而不是从头翻书。

适用范围先讲清楚:下面讲的机制、视图归属和字段单位,覆盖 10gR2 到 23ai 的主流版本;但个别列(比如 21c 才加入的 CPU/CPU_FG 列、FETCH FIRST 语法)存在版本差异,跨版本照抄前请以目标版本官方文档为准。

一、三个设计原则:视图为什么长这样

原则一:累计型 vs 当前状态型

V$ 视图其实只有两种视角:

  • 累计型:记录"从某个时刻起一共汇总了多少"。代表是 V$SYSTEM_EVENT(实例级等待汇总)、V$SYSSTAT(整体统计)。特点是数值只增不减,看的是历史总量。
  • 当前状态型:此刻的快照。代表是 V$SESSION_WAIT、V$SESSION。特点是反映"现在",刷新即变。

官方有一句很关键的提示:V$SESSION_WAIT 作为当前状态视图,粒度比 V$SESSION_EVENT、V$SYSTEM_EVENT 更细——它多了 P1、P2、P3 三个参数列,专门用来标识当前等待事件对应的具体对象(对象号、块号、文件号等)。

于是取舍就清楚了:

你要回答的问题 用哪类视图 典型视图
历史上等了什么、等了多久 累计型 V$SYSTEM_EVENT、V$SESSTAT
此刻到底卡在哪个东西上 当前状态型 V$SESSION_WAIT、V$SESSION
过去某一分钟发生了什么 采样历史型 V$ACTIVE_SESSION_HISTORY

原则二:前台与后台分开统计

等待事件统计里同时包含后台进程(LGWR、ARCH、DBWn 之类)和前台用户进程。但调优真正关心的是前台活动,所以从 11gR2 起,相关视图把实例活动拆成前台、后台两套统计——你可以看到 TIME_WAITED 和 TIME_WAITED_FG、AVERAGE_WAIT 和 AVERAGE_WAIT_FG 成对出现,_FG 后缀就是"仅前台会话"。

这一点在实战里非常重要:如果你排名时用的是全量 TIME_WAITED,很可能把后台归档 I/O、DBWR 写脏块的等待算到用户头上,方向从一开始就错了。排用户性能问题,优先看 _FG 列。

原则三:等待事件按"类"聚合

为了做高层分析,Oracle 把等待事件归成 12 个等待类:

Administrative、Application、Cluster、Commit、Concurrency、Configuration、Idle、Network、Other、Scheduler、System I/O、User I/O。

分类依据很有讲究——"通常适用于修复该类问题事件的一种通用解法"。这意味着一看到事件的 WAIT_CLASS,处方的方向基本就有了:

等待类 常见含义 通用解法方向
Application 应用代码或锁设计导致,如 enq: TX - row lock contention 查事务设计、加锁顺序、提交频率
Configuration 参数/配置问题,如 enq: HW - contention 检查高水位线、区分配、初始化参数
Concurrency 内部资源争用,如 latch、buffer busy 查热块、共享池、并发度
User I/O 用户发起的读写,如 db file sequential read 查索引、SQL、存储
System I/O 后台 I/O,如 DBWR、LGWR 相关 通常不针对用户 SQL 优化
Commit 提交相关的 redo 同步等待 检查 COMMIT_WRITE、日志 I/O
Idle 空闲等待,如 SQL*Net message from client 排名时一律剔除

二、五大家族:每族回答什么问题 + 怎么查

家族一:等待事件族(诊断主战场)

视图 回答的问题
V$SESSION 每个当前会话的信息,含阻塞会话 ID/状态/类型、等待对象详情(ROW_WAIT_OBJ#、ROW_WAIT_FILE#、ROW_WAIT_BLOCK#、ROW_WAIT_ROW#)
V$SESSION_WAIT 每个会话当前或最近在等什么;P1/P2/P3 参数列,粒度最细
V$SESSION_WAIT_HISTORY 每个活动会话最近 10 次等待事件的类型与等待时间
V$SESSION_EVENT 每个会话自启动以来累计等待了什么(会话退出即移除)
V$SESSION_WAIT_CLASS / V$SYSTEM_WAIT_CLASS 会话级 / 实例级的等待类聚合:每类等待的次数与时间
V$SYSTEM_EVENT 整个实例自启动以来所有会话的等待汇总——定方向的入口
V$EVENT_HISTOGRAM 每个事件的等待分布直方图——看分布,而不是均值
V$FILE_HISTOGRAM / V$TEMP_HISTOGRAM 每个数据文件 / 临时文件的单块读等待时间直方图
V$EVENT_NAME 全部等待事件的完整清单与等待类字典
V$ACTIVE_SESSION_HISTORY 每秒采样的活动会话历史——事件结束后仍可事后诊断

两条主线要记牢:

  • 实时抓现行走 V$SESSION_WAIT。P1/P2/P3 的含义随事件不同而不同——对 db file sequential read,P1=文件号、P2=块号、P3=块数;对 enq: 类,P1/P2 编码锁类型与模式。想知道某个事件的 P1/P2/P3 具体是什么,直接查字典:
SELECT NAME, PARAMETER1, PARAMETER2, PARAMETER3, WAIT_CLASS
FROM   V$EVENT_NAME
WHERE  NAME LIKE 'enq%';
  • 事后回溯走 V$SYSTEM_EVENT 和 ASH。查字典想找有哪些 enq: 等待,一句 SELECT event FROM v$event_name WHERE event LIKE 'enq%' 就够了,不用翻文档。

家族二:系统统计族(数据库整体干了多少活)

  • V$SYSSTAT:覆盖回滚、逻辑与物理 I/O、解析等整体统计。缓冲区命中率这类比率就是拿它的数据算的。
  • V$SESSTAT + V$STATNAME:会话级统计加名字字典,按 STATISTIC# 关联。官方示例是用 session uga memory 和 session uga memory max 统计所有会话的总内存消耗。
  • V$SYS_TIME_MODEL / V$SESS_TIME_MODEL:时间模型,回答"时间花哪了",也是后面算"等待占 DB time 百分比"的那把统一尺子。

查时间模型的起手式:

SELECT STAT_NAME, ROUND(VALUE/1000000, 1) SECONDS
FROM   V$SYS_TIME_MODEL
WHERE  STAT_NAME IN ('DB time', 'DB CPU',
                     'background elapsed time', 'parse time elapsed')
ORDER  BY SECONDS DESC;

DB time 是整段分析的基准——它代表"所有前台会话花在数据库上的总时间(含等待)"。后面所有百分比都要除以它。

家族三:争用诊断族(资源卡在哪)

  • V$LATCH:每个 latch 的请求与等待统计。latch 等待上升意味着并发度下降,典型场景是过度硬解析打爆 library cache latch。

  • V$LATCH_CHILDREN:子 latch 级定位。它包含 V$LATCH 的全部列,外加 CHILD#(同一父 latch 下子锁编号)。定位链条非常经典:

    某个子 latch 的 GETS/MISSES/SLEEPS 显著高于其他子 latch → 它就是争用点 → 经 ADDR 列关联 X$BH 找到热点块 → 用文件号+块号查 DBA_EXTENTS 定位到具体段。

    一条可用的子 latch 排行查询:

SELECT LATCH#, CHILD#, NAME, GETS, MISSES, SLEEPS, WAIT_TIME
FROM   V$LATCH_CHILDREN
WHERE  SLEEPS > 0
ORDER  BY SLEEPS DESC
FETCH FIRST 10 ROWS ONLY;   -- 11g 及以前用 WHERE ROWNUM <= 10
  • V$ENQUEUE_STAT:每个 enqueue 的请求、等待次数与等待时间。锁类问题(TX、TM、HW 等)先看它。
  • V$WAITSTAT:按 buffer class 统计块争用。注意它只在计时统计开启时才会更新,而且 TIME 列单位是厘秒(centiseconds,1/100 秒)。
SELECT CLASS, COUNT, TIME,
       ROUND(TIME*100/SUM(TIME) OVER (), 1) PCT
FROM   V$WAITSTAT
WHERE  TIME > 0
ORDER  BY TIME DESC;

CLASS 告诉你被抢的是哪种块:data block、segment header、undo header、undo block、data block sequence 等——这是后面选优化手段的分岔口。

家族四:I/O 与段级族(把等待落到具体对象)

  • V$FILESTAT:每个文件的 I/O 次数和平均读时间(AVG_READ_TIME 等),磁盘热点一查便知。
  • V$ROLLSTAT:每个回滚/undo 段的统计。
  • V$SEGMENT_STATISTICS:段级统计。它是"从系统在等"到"哪张表在挨打"的收口工具。
SELECT OWNER, OBJECT_NAME, SUBOBJECT_NAME, STATISTIC_NAME, VALUE
FROM   V$SEGMENT_STATISTICS
WHERE  STATISTIC_NAME IN ('buffer busy waits', 'gc buffer busy', 'physical reads')
ORDER  BY VALUE DESC
FETCH FIRST 10 ROWS ONLY;

使用前提:段级统计只在 STATISTICS_LEVEL 为 TYPICAL 或 ALL 时收集。如果这个参数被调成 BASIC,V$SEGMENT_STATISTICS 可能查不到有意义的累计值——这是排查"为什么收口收不动"时第一个要确认的点。

家族五:历史采样族(事后取证)

主力就一个:V$ACTIVE_SESSION_HISTORY(ASH)。官方定义很精确——每秒采样一次,只有"正在 CPU 上运行"或"正在等待非 Idle 类事件"的会话才算活动会话,每个采样点一行。它有 100 多列,大部分列与 V$SESSION 相同,另外还多了 SQL_ID、SQL_PLAN_HASH_VALUE、BLOCKING_SESSION、EVENT、WAIT_CLASS 等。

它的价值在于事后性——问题过去了、会话退出了,照样能查。累计视图只能告诉你"出事期间等了很多",ASH 能告诉你"出事那一分钟,会话卡在哪个等待事件、正在执行哪条 SQL"。

SELECT SAMPLE_TIME, SESSION_ID, SESSION_TYPE, EVENT, WAIT_CLASS,
       SQL_ID, BLOCKING_SESSION
FROM   V$ACTIVE_SESSION_HISTORY
WHERE  SAMPLE_TIME > SYSTIMESTAMP - INTERVAL '10' MINUTE
AND    SESSION_TYPE = 'USER'
ORDER  BY SAMPLE_TIME DESC;

按等待事件聚合,就能还原"过去半小时谁在等":

SELECT N.NAME, COUNT(*) SAMPLES
FROM   V$ACTIVE_SESSION_HISTORY A, V$EVENT_NAME N
WHERE  A.EVENT_ID = N.EVENT_ID
AND    A.SAMPLE_TIME > SYSTIMESTAMP - INTERVAL '30' MINUTE
AND    N.WAIT_CLASS <> 'Idle'
GROUP  BY N.NAME
ORDER  BY SAMPLES DESC;

两个必须知道的边界:一是它是环形缓冲区,实例越繁忙、同样容量保留的历史越短,突发故障要尽快取证;二是 ASH 需要 STATISTICS_LEVEL 为 TYPICAL 或 ALL。历史数据还会随 AWR 快照落进 DBA_HIST_ACTIVE_SESS_HISTORY,跨天回溯要查那张。

三、实战 SOP:三步下钻法(可直接照抄)

单个视图只能给症状,官方推荐组合下钻。为什么必须组合?因为每层分辨率不同:

  • V$SYSTEM_EVENT 像体检报告上的"肝功能异常"——告诉你哪个系统出了问题,但不告诉你是哪根管道堵了;
  • 专项视图像造影——能看到具体类型和对象;
  • 段级统计才是病灶定位。

跳层直接查细视图,容易在海量对象里捞针。先用粗视图把范围缩到一类等待,再逐层收敛,才是这套流程的设计意图。

第 0 步(前置):确认统计开关

SELECT NAME, VALUE FROM V$PARAMETER
WHERE  NAME IN ('statistics_level', 'timed_statistics');
  • 11g 及以后:timed_statistics 已废弃,实际由 statistics_level 决定。它若为 BASIC,很多计时和 ASH、段级统计都会失效——先把这个问题解决,否则后面全是 0。
  • 老版本(10g 及更早):需要 TIMED_STATISTICS=TRUE,否则 V$SYSTEM_EVENT 的时间列全是 0。

第 1 步:V$SYSTEM_EVENT 定方向

把等待事件按等待时间排名,剔除 Idle 类,算 Top 事件占非空闲等待总时间的百分比。AWR 报告的 Top 5 Timed Events 段已经自动做完了这一步。手工版(官方示例):

SELECT E.EVENT, N.WAIT_CLASS, E.TIME_WAITED_MICRO,
       ROUND(E.TIME_WAITED_MICRO*100/S.DBTIME, 1) PCT_DB_TIME
FROM   V$SYSTEM_EVENT E, V$EVENT_NAME N,
       (SELECT VALUE DBTIME FROM V$SYS_TIME_MODEL WHERE STAT_NAME = 'DB time') S
WHERE  E.EVENT_ID = N.EVENT_ID
AND    N.WAIT_CLASS NOT IN ('Idle', 'System I/O')
ORDER  BY PCT_DB_TIME DESC;

它把"症状"(等待事件)和"统一尺子"(DB time)焊在了一起:每个事件占了总工作负载的几成,一目了然。相比只看绝对等待时间,用百分比能立刻排除"数值大、占比小"的噪声。

排用户问题的改进版——只看前台等待:

SELECT E.EVENT, N.WAIT_CLASS,
       ROUND(E.TIME_WAITED_FG*100/S.DBTIME, 1) PCT_DB_TIME_FG
FROM   V$SYSTEM_EVENT E, V$EVENT_NAME N,
       (SELECT VALUE DBTIME FROM V$SYS_TIME_MODEL WHERE STAT_NAME = 'DB time') S
WHERE  E.EVENT_ID = N.EVENT_ID
AND    N.WAIT_CLASS NOT IN ('Idle', 'System I/O')
ORDER  BY PCT_DB_TIME_FG DESC;

第 2 步:专项视图找对象

拿到 Top 事件后,按事件类型选专项视图。几个高频事件的固定路线:

Top 事件 进哪张专项视图 看什么
buffer busy waits / gc buffer busy V$WAITSTAT + V$SESSION 块类型(CLASS)、ROW_WAIT_OBJ# 争用对象
latch: ... / library cache lock V$LATCH_CHILDREN 子 latch 的 SLEEPS 排行
enq: TX - row lock contention V$ENQUEUE_STAT + V$LOCK 锁模式、阻塞者会话
db file sequential read V$FILESTAT 哪个文件的读时间最长
通用"到底卡在谁身上" V$SESSION(BLOCKING_SESSION) 阻塞链

第 3 步:V$SEGMENT_STATISTICS 收口

从"系统在等"到"哪张表在挨打",就差这一步。选出造成等待的 Top 段,把优化火力集中到具体的表或索引上。

四、三条完整排障路线(照着走)

路线 A:buffer busy waits 热点块定位

-- ① 确认块类型(在抢什么)
SELECT CLASS, COUNT, TIME FROM V$WAITSTAT WHERE TIME > 0 ORDER BY TIME DESC;

-- ② 实时找到争用对象(正卡住的行/块)
SELECT S.SID, S.SERIAL#, S.EVENT, S.STATE,
       S.ROW_WAIT_OBJ#, S.ROW_WAIT_FILE#, S.ROW_WAIT_BLOCK#, S.ROW_WAIT_ROW#
FROM   V$SESSION S
WHERE  S.ROW_WAIT_OBJ# <> -1;

-- ③ 收口到段
SELECT OWNER, OBJECT_NAME, SUBOBJECT_NAME, STATISTIC_NAME, VALUE
FROM   V$SEGMENT_STATISTICS
WHERE  STATISTIC_NAME = 'buffer busy waits'
ORDER  BY VALUE DESC FETCH FIRST 10 ROWS ONLY;

判读:如果 CLASS 是 segment header,多半是插入导致的段头/区分配争用(可考虑更大的区或 ASSM);如果是 undo header,指向 undo 争用;如果是 data block,则是数据块本身被并发抢,要落到具体表上做分区、调整 PCTFREE 或应用层拆分。这条线走完,优化才有靶子。

路线 B:latch 争用定位热点块

-- ① 子 latch 排行
SELECT LATCH#, CHILD#, NAME, GETS, MISSES, SLEEPS, WAIT_TIME, ADDR
FROM   V$LATCH_CHILDREN
WHERE  SLEEPS > 0
ORDER  BY SLEEPS DESC FETCH FIRST 10 ROWS ONLY;

-- ② 用上面拿到的 ADDR 关联 X$BH,找热点块
SELECT HLADDR, FILE#, DBABLK, CLASS, STATE, TCH
FROM   X$BH
WHERE  HLADDR = HEXTORAW('<上一步的 ADDR>')
ORDER  BY TCH DESC;

-- ③ 用文件号+块号定位段
SELECT OWNER, SEGMENT_NAME, SEGMENT_TYPE
FROM   DBA_EXTENTS
WHERE  FILE_ID = <文件号>
AND    <块号> BETWEEN BLOCK_ID AND BLOCK_ID + BLOCKS - 1;

判读:如果争用的是 library cache 相关子 latch,且伴随大量硬解析,方向应转向 SQL 共享(绑定变量、CURSOR_SHARING、共享池大小),而不是去优化某张表。

路线 C:I/O 热点定位

SELECT D.NAME, F.PHYRDS, F.PHYBLKRD, F.AVG_READ_TIME
FROM   V$FILESTAT F, V$DATAFILE D
WHERE  F.FILE# = D.FILE#
ORDER  BY F.AVG_READ_TIME DESC FETCH FIRST 10 ROWS ONLY;

判读:AVG_READ_TIME 明显偏高的文件就是存储热点。但要结合 V$FILE_HISTOGRAM 看分布——平均 5 毫秒里可能藏着 1% 的 5 秒长尾,那才是真正的痛点。

五、版本与参数注意事项(跨版本照抄前必看)

事项 老版本(10g 及更早) 新版本(11g 及以后)
计时开关 参数 TIMED_STATISTICS=TRUE 参数废弃,改由 STATISTICS_LEVEL(TYPICAL/ALL)控制
前台/后台分列 无 _FG 列 V$SYSTEM_EVENT 等有 TIME_WAITED_FG、AVERAGE_WAIT_FG
段级统计 依赖计时统计 需 STATISTICS_LEVEL ≥ TYPICAL
ASH 10gR2 引入 12c 起按 PDB 有 CON_ID 维度
FETCH FIRST 语法 不支持,用 ROWNUM 12c 起支持

单位换算别搞混(这是最容易算错的):

  • V$SYSTEM_EVENT.TIME_WAITED:厘秒(1/100 秒);TIME_WAITED_MICRO:微秒。
  • V$WAITSTAT.TIME:厘秒。
  • V$SYS_TIME_MODEL.VALUE:微秒。
  • V$FILESTAT.AVG_READ_TIME:厘秒/次。

六、几个常见的坑

  1. 背视图不背"回答什么问题"。 几百个视图按字段背必然崩;按家族和问题背,地图一辈子受用。
  2. 忽略统计开关。 时间列全 0 时,"谁等得最久"退化成"谁等待次数最多",方向直接跑偏。新版本上要检查的是 STATISTICS_LEVEL。
  3. 不剔除 Idle 类就排名。 SQL*Net message from client 会常年霸榜,把它当瓶颈查,能查到明年。
  4. 只看全量、不看前台。 把后台 DBWR/ARCH 的 I/O 等待算到用户头上,白忙一场。排用户问题优先看 _FG 列。
  5. 只看均值不看分布。 平均等待 5 毫秒可能藏着 1% 的 5 秒长尾,V$EVENT_HISTOGRAM、V$FILE_HISTOGRAM 就是为这个场景准备的。
  6. 停在事件层不落地段。 buffer busy waits 报了一堆,没走完 V$WAITSTAT → ROW_WAIT_OBJ# → V$SEGMENT_STATISTICS 这条收口线,优化火力就还是散的。
  7. 拖到事后才取 ASH。 它是环形缓冲区,越忙保留越短,突发故障要尽快取证,跨时段回溯改用 DBA_HIST_ACTIVE_SESS_HISTORY。

七、把判断标准也固化下来

排障时最容易犯的另一个错,是"看到排名第一就冲上去优化"。官方给了一条很实用的标尺:

  • 某事件在 2 小时周期里只等了 30 秒,即使排名最高也不值得深究;
  • 某事件在 45 分钟周期里等了 30 分钟,那就值得全力调查。

排名是相对的,绝对时长才是投入的依据。 建议把它写进你的 SOP:先算"该事件等待时间 ÷ 分析周期",占比超过 10%~20% 再动手。

八、动手练一练

  1. 把这五大家族的表格抄进自己的运维手册,每个视图旁标一句话:"它回答什么问题"。以后排障先翻这页,再查具体字段。
  2. 在测试库跑一遍第 1 步的百分比查询,找出占 DB time 比例最高的 3 个等待事件,逐个判断属于 12 类中的哪一类,想想各自的"通用解法"。
  3. 模拟一次完整下钻:构造高频并发更新制造 buffer busy waits,按 V$SYSTEM_EVENT → V$WAITSTAT → V$SESSION(ROW_WAIT_OBJ#)→ V$SEGMENT_STATISTICS 四步走完全程,把最后定位到的段名记下来。
  4. 加一条压力测试,让硬解析飙升,再走一遍路线 B,观察 library cache latch 的 SLEEPS 如何变化。

九、读完可以带走什么

把流程固定成肌肉记忆:第 0 步确认统计开关 → 第 1 步 V$SYSTEM_EVENT/AWR Top 5 定方向 → 第 2 步进对应家族的专项视图找对象 → 第 3 步落到段或 SQL 收口。

最大的体会是——视图不是用来记的,是用来查的;但家族地图必须刻在脑子里。否则连"该查哪个"都想不起来,手边再多视图也用不上。地图在手,字段随查随用,排障这件事就从"翻书碰运气"变成了"按图索骥"。

posted on 2026-10-12 22:01  刘子毅  阅读(10)  评论(0)    收藏  举报

导航