别背 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:厘秒/次。
六、几个常见的坑
- 背视图不背"回答什么问题"。 几百个视图按字段背必然崩;按家族和问题背,地图一辈子受用。
- 忽略统计开关。 时间列全 0 时,"谁等得最久"退化成"谁等待次数最多",方向直接跑偏。新版本上要检查的是
STATISTICS_LEVEL。 - 不剔除 Idle 类就排名。
SQL*Net message from client会常年霸榜,把它当瓶颈查,能查到明年。 - 只看全量、不看前台。 把后台 DBWR/ARCH 的 I/O 等待算到用户头上,白忙一场。排用户问题优先看
_FG列。 - 只看均值不看分布。 平均等待 5 毫秒可能藏着 1% 的 5 秒长尾,
V$EVENT_HISTOGRAM、V$FILE_HISTOGRAM就是为这个场景准备的。 - 停在事件层不落地段。 buffer busy waits 报了一堆,没走完
V$WAITSTAT → ROW_WAIT_OBJ# → V$SEGMENT_STATISTICS这条收口线,优化火力就还是散的。 - 拖到事后才取 ASH。 它是环形缓冲区,越忙保留越短,突发故障要尽快取证,跨时段回溯改用
DBA_HIST_ACTIVE_SESS_HISTORY。
七、把判断标准也固化下来
排障时最容易犯的另一个错,是"看到排名第一就冲上去优化"。官方给了一条很实用的标尺:
- 某事件在 2 小时周期里只等了 30 秒,即使排名最高也不值得深究;
- 某事件在 45 分钟周期里等了 30 分钟,那就值得全力调查。
排名是相对的,绝对时长才是投入的依据。 建议把它写进你的 SOP:先算"该事件等待时间 ÷ 分析周期",占比超过 10%~20% 再动手。
八、动手练一练
- 把这五大家族的表格抄进自己的运维手册,每个视图旁标一句话:"它回答什么问题"。以后排障先翻这页,再查具体字段。
- 在测试库跑一遍第 1 步的百分比查询,找出占 DB time 比例最高的 3 个等待事件,逐个判断属于 12 类中的哪一类,想想各自的"通用解法"。
- 模拟一次完整下钻:构造高频并发更新制造 buffer busy waits,按
V$SYSTEM_EVENT → V$WAITSTAT → V$SESSION(ROW_WAIT_OBJ#)→ V$SEGMENT_STATISTICS四步走完全程,把最后定位到的段名记下来。 - 加一条压力测试,让硬解析飙升,再走一遍路线 B,观察 library cache latch 的
SLEEPS如何变化。
九、读完可以带走什么
把流程固定成肌肉记忆:第 0 步确认统计开关 → 第 1 步 V$SYSTEM_EVENT/AWR Top 5 定方向 → 第 2 步进对应家族的专项视图找对象 → 第 3 步落到段或 SQL 收口。
最大的体会是——视图不是用来记的,是用来查的;但家族地图必须刻在脑子里。否则连"该查哪个"都想不起来,手边再多视图也用不上。地图在手,字段随查随用,排障这件事就从"翻书碰运气"变成了"按图索骥"。
浙公网安备 33010602011771号