
做了十几年数据库运维我养成一个习惯谁发我一份AWR报告我先不急着从头翻到尾而是直接跳到I/O相关的段落扫一眼再倒回去看Top事件和SQL。为什么这么干因为大多数OLTP系统的变慢根因要么在I/O要么在SQL而这两者往往又是连在一起的——SQL写得低效就会产生大量多余的物理读反过来把I/O拖垮存储I/O扛不住再好的SQL也跑不出像样的延迟。AWRAutomatic Workload Repository作为Oracle自带的性能体检报告其中的I/O分析就是串联这两件事的那根主线。这篇文章我就从实操角度把AWR里I/O分析怎么看、怎么定位、怎么下手解决完整捋一遍适合刚接触AWR的运维新人也适合那些拿到几十页AWR不知道从哪里下手的DBA。1. 拿到AWR报告I/O分析到底在看什么1.1 先分清是I/O是瓶颈还是I/O在背锅拿到一份AWR第一步不是去抠哪个等待事件的数字而是先看报告开头最基础的两个时间Elapsed Time和DB Time。这两个数的关系决定了后面整个分析的方向。Elapsed Time是报告区间的真实经过时间比如快照间隔1小时它就是3600秒。DB Time则是所有会话在数据库内部消耗时间的总和等于所有会话CPU时间加等待时间的累加值。如果DB Time远大于Elapsed Time说明系统长时间处于多个会话叠加的高负载状态这种场景下性能问题一定存在接下来就是找时间耗在哪了。如果DB Time和Elapsed Time差不多大概率是负载不高某个会话或某个操作偶发卡顿这时候AWR均值类指标反而不敏感需要改用ASH做定点分析。判断I/O是不是主要矛盾我的习惯性操作是看两个信号一是Top 10 Foreground Events里面I/O类等待事件占DB Time的比例二是DB CPU占DB Time的比例。如果DB CPU占比很高比如接近或超过70%说明系统主要时间在烧CPUI/O难看不代表它是根因真正的方向是SQL执行计划、并发争用和负载本身。反之如果I/O类等待事件排在前三且等待时间占比明显高于CPU这时候I/O才值得作为头号嫌疑人。这里有个最常见的误判需要提前说清楚高CPU场景下AWR的I/O指标经常也非常难看。比如一条SQL因为执行计划走错重复扫描大表物理读暴涨同时CPU也烧得很高。这种场景大家都很容易把注意力放在物理读那么多是不是存储不行上然后去找存储团队开会。但实际上I/O只是结果SQL才是原因。另一种场景恰好反过来存储延迟已经20毫秒以上I/O等待占比很高但SQL本身是中规中矩的索引访问这时候就算你把SQL从头到尾优化一遍延迟也降不下来问题在存储。所以第一步永远是分层判断而不是拿一个指标下结论。1.2 等待事件按类分组I/O问题出在哪一层Oracle的等待事件从大类上分和I/O直接相关的主要有三个classUser I/O、System I/O、以及隶属于Commit或Configuration类的redo相关等待。这三组等待的含义完全不同混在一起看很容易误判。User I/O class是前台会话直接发出的I/O请求用户能感知比如db file sequential read单块读典型场景是按索引键值读取数据块、db file scattered read多块读典型场景是全表扫描或索引快速全扫、direct path read直接路径读11g之后并行查询、排序溢出、临时段读取都属于它。这类等待高直接表现为业务响应慢也是AWR分析的主战场。System I/O class大多是后台进程的I/O比如db file parallel read、control file read/write、log file parallel write。这类等待通常不直接体现在用户响应上但如果它异常升高往往意味着底层存储已经出现整体性的瓶颈后台进程在排队等I/O。你只看前台Top事件可能觉得还行一旦System I/O升高需要立刻警觉因为它通常是更大问题的先兆。还有一组容易被忽略的redo相关等待log file sync、log file parallel write、log file switch。log file sync是前台会话提交事务后等待LGWR把redo日志落盘并返回确认commit越频繁、redo存储越慢这个等待就越高。log file parallel write是LGWR后台写日志的等待它升高说明redo写路径的存储能力不足。这两类等待虽然不是I/O Profile里的物理读但本质就是I/O问题而且是事务型系统最伤的I/O问题之一。判断方向时要把这三类事件分开看User I/O高优先查SQL访问路径和存储延迟System I/O高优先查存储整体负载redo类等待高优先查提交频率和日志文件所在的存储。1.3 I/O分析的标准路径六步定位法我给自己总结了一套AWR I/O分析的固定套路基本不看心情拿到报告就按顺序走。第一步看Top 10 Foreground Events判断系统是否存在I/O类等待属于哪种type。第二步看I/O Profile了解系统整体的读、写、redo量级判断负载类型是大量小I/O还是少量大I/O。第三步看Tablespace and Datafile I/O Statistics定位物理I/O集中在哪些数据文件以及单次I/O的平均延迟。第四步看Segment Statistics找到物理读最集中的表、索引或分区对象。第五步看SQL Statistics把产生大量物理读的Top SQL拎出来。第六步交叉验证如果平均读延迟偏高结合操作系统层面的iostat/sar数据来区分是存储问题还是SQL问题。这六步每一步都是有因果关系的不能跳。很多新手喜欢直接跳到第五步看SQL看完就抓一条去优化也不管存储延迟是不是已经爆炸——如果存储本身就慢优化SQL只是在给存储争取喘息时间治标不治本。反过来只看存储不看SQL可能换个高端存储过几个月SQL问题又把你拖回原样。AWR的I/O分析最值钱的地方就是把这些信息放在同一份报告里让你有能力判断到底谁该为慢负责。2. AWR中I/O相关核心指标逐个拆解2.1 Top 10 Foreground Events第一眼锁定方向每个看过AWR的人都知道这段但很多人只盯着Event那一列Waits和Average Wait Time往往被带过。这其实是最高频出现的低级失误。Waits代表等待次数Average Wait Time代表单次等待时间两者相乘才是总等待时间而总等待时间占DB Time的比例才是这个事件的分量。以最常见的db file sequential read为例。如果Waits有几十万次但Average Wait Time只有1毫秒总等待时间其实不大说明存储没问题只是访问次数多——这时候方向是降低I/O次数比如优化SQL减少回表、增加索引覆盖。如果Average Wait Time到了15毫秒以上那就说明单次读本身就慢存储延迟是主问题这时候就算把I/O次数降下来剩余部分依然会拖慢查询。我见过很多团队只看总等待时间排名忽略平均等待结果把一个SQL反复优化到极致延迟还是没降下来最后才发现存储延迟20毫秒SQL再怎么优化也突破不了物理下限。再说几个常见I/O类事件的实际含义。db file scattered read经常被翻译成散列读名字有迷惑性它实际上是多块读通常和全表扫描、index fast full scan绑定。一个系统如果scattered read大量出现第一反应不是存储而是为什么有这么多大范围扫描去Segment和SQL里找罪魁。direct path read在11g之后尤其值得注意并行查询、临时表排序、hash join都会触发如果它大面积出现可能是某个SQL并行度设置过高或者temp表空间压力过大。log file sync前面说过commit频繁和redo写慢都会导致它升高判断方法是同时看log file parallel write以及AWR里Redo size的量级如果redo size不大但log file sync高多半是commit太频繁如果redo size大且log file parallel write也高多半是存储写日志太慢。2.2 I/O Profile看清读写全貌往下翻到I/O Profile这一节看到的是一大排数字Redo size、Logical read、Physical read、Physical write、Read IO requests、Write IO requests等等。这一节很容易被当成仅供参考的数据跳过去但实际上它是判断负载模型的黄金数据。第一个要算的是Buffer Cache Hit Ratio也就是逻辑读中发生在内存里、不需要物理I/O的比例。AWR报告里其实会直接给出这个值但我要说的是它的使用门槛命中率高不代表I/O没问题命中率低也不一定就是SGA太小。举个例子一条SQL每次执行要扫一张1GB的表但表一直在内存里命中率是100%可它每次把整个buffer cache翻一遍污染其他数据块这种场景命中率很高但I/O和性能都很糟。反过来一个OLTP系统命中率只有95%但如果物理读的请求量很小、延迟也很低那就完全不是问题。所以I/O Profile里我更关注每秒Physical Read Requests和每秒Physical Read Bytes这两个绝对值而不是命中率这个比例。通过它们可以判断I/O请求是小而密还是大而粗对后续方向判断非常关键。Redo size也很重要它反映事务日志产生的速率。如果系统每秒产生几十MB的redo存储又只能支撑几十MB/s的写入那log file parallel write迟早会成为瓶颈。I/O Profile里的数据本质上是给后续所有分析定基调的一个每秒只有一百次物理读的系统哪怕全表扫描再频繁影响也可控;一个每秒上万次物理读的系统哪怕单次只有2毫秒累积起来也够数据库喝一壶。数字量级先看清再谈优化策略。2.3 表空间与数据文件统计I/O到底打在哪个文件上AWR中Tablespace IO Stats和Datafile IO Stats这一段是按表空间、按数据文件统计物理读写量的默认按物理读、物理写、I/O请求次数等排序。这一步的意义在于把问题从整个系统I/O慢缩小到某个文件慢。具体看什么三个字段分别是Av Rd ms平均读延迟、Av Blks/Rd平均每次读的块数、PhyRd Per sec每秒物理读次数。Av Rd ms是判断存储延迟的直接证据正常情况下SSD在1到2毫秒机械盘在10到15毫秒左右如果某个数据文件的Av Rd ms异常偏高比如30毫秒以上基本上可以断定这个文件所在的存储资源存在严重争用或者硬件能力不足。Av Blks/Rd则能告诉你这个文件的I/O模式接近1说明全是单块读典型OLTP索引访问几十甚至上百说明多块读很明显文件上有大面积扫描型操作。我还会顺手看文件的物理读速率。如果某个文件每秒物理读只有几十次但Av Rd ms很高这个文件不值得过多关注因为影响面有限。真正要命的是Av Rd ms高、每秒物理读也高的文件它才是整个系统I/O瓶颈的最大贡献者。很多存储在多个表空间共用一个磁盘组Datafile统计里会多个文件同时表现出高延迟这时候就基本可以判断是存储共享层面的问题了。如果只有某一个文件延迟高而其他文件都很正常可以考虑是不是该文件落在一个性能较差的磁盘上或者是热块集中在同一个文件。2.4 段级统计与日志类I/O揪出具体对象Segment Statistics是AWR里专门按段对象表、索引、分区等汇总I/O和Buffer Busy等指标的区域其中有Top Segments by Physical Reads、Top Segments by Physical Writes等分类。这一步能把问题从数据文件再缩小到具体是哪个表、哪条索引在制造物理读。最典型的场景是Top Segments by Physical Reads排名第一的是一个普通B-tree索引它的Physical Reads异常高。这时候就要想了索引为什么会做大量物理读要么索引被频繁范围扫描要么索引的选择性差导致SQL优化器大量走索引扫描但每次只取少量行要么这个索引长期未重建叶子块分裂导致索引访问多读了额外分支块。顺着这个对象再去关联SQL Statistics里的Top SQL基本就能把谁在打它找出来。日志类I/O的判断需要一点经验。我看redo相关问题时除了Top事件里的log file sync/log file parallel write还会看I/O Profile里的Redo size以及AWR尾部关于redo log切换相关的统计。如果Redo size速率高加上log file parallel write等待高说明redo文件所在存储写入能力不足如果Redo size一般但log file sync高大概率是应用commit过于频繁每次提交都强制一次日志落盘这是应用行为问题不是存储问题。还有一种隐藏情况是redo log文件太小导致log file switch频繁检查AWR中是否有log file switch (checkpoint incomplete)这类等待有的话需要增加日志文件大小。3. 实操案例一次存储性能问题排查3.1 现象与初步判断说个去年帮朋友处理过的真实案例。一个小型OLTP系统Oracle 11.2.0.4跑在VMware虚拟化的共享存储上大概40到50个并发连接。现象是业务高峰期查询响应从正常的200毫秒左右恶化到2秒以上应用前台报了慢查询告警开发团队怀疑是SQL写得有问题要求DBA给出优化建议。拿到1小时高峰期AWR后我没有直接去看SQL而是按上面的路径先扫整体。Elapsed Time 3600秒DB Time达到了8900秒说明整个区间系统基本处于高负载运转。Top 5 Foreground Events里排第一的是db file sequential read占DB Time约43%单次平均等待19毫秒排在第二的才是DB CPU约25%log file sync占7%平均等待12毫秒。这个分布很清晰地指向了I/O路径而且是单块读延迟高不是扫描型读多的问题。3.2 AWR关键指标解读接着看I/O Profile每秒Physical Read Requests大概在800次左右每秒Physical Read Bytes不算大说明系统不是以扫描为主的海量读类型而是大量的小型单块读请求。这种负载形态下一次读的延迟对响应时间影响极其敏感——每次索引访问都要做好几次物理读每次多花十几毫秒累积起来业务响应当然爆炸。Datafile IO Stats印证了这一点排在前面的三个数据文件Av Rd ms都在18到20毫秒出头属于明显偏高的水平。当时我心里已经有了初步判断存储层面延迟确实有问题。但问题是否完全在存储还得验证因为这些文件上也有相当高的物理读次数如果SQL本身访问路径有问题比如本来命中索引却大量回表那也会放大物理读的次数加重存储负担。继续看Segment Statistics物理读最集中的是一个核心业务表上的普通B-tree索引。再把SQL Statistics翻出来Top SQL里排第一的是一个高频执行的SELECT执行计划是走索引回表每次执行逻辑读不到100但伴随几十次物理读执行频率很高。这就把链路完整串起来了高频SQL 频繁回表产生大量物理读请求叠加存储单次I/O延迟高最终导致响应时间直线恶化。3.3 定位根因与解决过程到这里根因就不是单一问题了。我把它分成两个层次存储层是共享存储出现了延迟虚拟化环境下宿主机上其他虚机的I/O争抢明显存储的avg service time比基线高出一倍多SQL层是这条高频查询虽然走了索引但索引字段组合不完整需要回表取列导致每次执行都产生大量物理读。这两个因素互为放大器存储慢让物理读更疼SQL回表多让存储更忙。解决的先后顺序很关键。我的动作是先优化SQL因为这个成本最低、见效最快。把原SQL改成覆盖索引方案把需要回表取的列并进索引消除回表。这一步实施后这条SQL每次执行的物理读直接从几十次降到了个位数。由于它是执行频率最高的SQL整个系统的每秒Physical Read Requests从800次左右一下降到了200次出头db file sequential read的总等待时间大幅缩水。响应时间虽然还有波动但已经从2秒降到了600毫秒左右。接着处理存储层。SGA调优和SQL改写都做过了物理读已经明显下降但剩下的部分延迟依然有十几毫秒。我让运维同事在相同高峰时段打了一份系统层I/O统计交叉验证后确认共享存储存在邻居I/O争抢。业务上协调将几个热数据库迁移到更高性能的存储层迁移后db file sequential read单次平均等待降到4毫秒左右业务响应恢复正常高峰期观察DB Time从8900秒降到3000秒出头用户侧感知明显好转。3.4 这个案例给的三条经验第一条千万别只看Top SQL就把问题定性成SQL太差。这个案例如果只优化SQL不碰存储物理读次数降到200以后单次19毫秒的延迟依然会让业务卡顿反过来如果只换存储不优化SQL高峰期每秒800次物理读依然会持续给存储施压换再好的设备也扛不住这种低效访问放大。两条腿走路才是正解。第二条AWR的均值有时候会骗人。报告区间内db file sequential read平均19毫秒但如果你拉出dba_hist_event_histogram按等待时间分布看会发现真正的峰值出现在某20分钟区间其他时间其实还好。这种分布信息对定位触发条件是很有用的尤其适合用来判断是不是某个定时任务、批量作业在高峰时段突然拉高I/O压力。第三条和运维团队联动时最好提供同一时间窗的AWR和OS级数据做对照。AWR解决的是数据库视角的I/O等待是多少操作系统层面解决的是存储硬件视角的延迟和队列长度是多少。只给一份AWR存储团队没法判断是自己设备的问题还是数据库生成I/O的方式有问题我在实践中都是把两份报告的时间对齐再去谈归属效率高很多。4. 常见问题与排查技巧实录4.1 高频I/O问题的特征对照做AWR I/O分析久了很多场景是重复出现的。我把常见情况整理成一个速查对照方便快速套用。现象可能原因下一步排查方向db file sequential read等待高且Avg超过10ms存储延迟偏高或单文件热点查Datafile IO Stats、对照OS iostatdb file sequential read等待高但Avg在5ms以内I/O次数过多重复单块读查Segment和SQL看是否回表过多db file scattered read等待异常升高大量全表扫描或index fast full scan查Top SQL执行计划direct path read飙升伴随并行度大的SQL并行查询或排序溢出查SQL并行度设置、temp空间log file sync和log file parallel write同时高redo存储写入慢查redo文件是否与数据文件同盘log file sync高但redo size不高应用commit过于频繁需从应用层优化事务批量提交buffer cache命中率极低物理读海量SGA过小或执行计划低效对比逻辑读、SAP调整大小temp表空间I/O巨大hash join或排序量过大查Top SQL的temp空间使用这些特征基本覆盖了我在生产环境遇到的九成以上I/O类问题。需要说明的是这只是一个初筛工具最后还是要靠报告里的具体数值交叉确认。4.2 这几个坑我踩过不止一次第一个坑是迷信Buffer Cache Hit Ratio。我在刚开始做性能分析那几年看到命中率99.5%就觉得I/O没毛病后来被打脸几次才明白命中率是个覆盖面广但对延迟不敏感的大盘指标。一个每秒做一万次逻辑读、其中一千次物理读且延迟20毫秒的系统命中率也有90%可I/O问题已经很严重了。正确的做法是优先看每秒物理读请求次数和单次读延迟这两个才是和用户体验直接关联的数字。第二个坑是只看平均值忽略尖峰。AWR是时间段的汇总一切数据都是平均值。某个等待事件的Avg Wait Time可能只有3毫秒但在业务高峰的十几分钟里可能冲到50毫秒恰恰是那十几分钟让用户暴走。所以我做重要系统的AWR分析一定会顺带看一眼dba_hist_event_histogram的历史直方图或者用ASH查高峰时段的等待事件分布避免被被平均骗过去。第三个坑是逻辑读双击在SQL层面修正后AWR里物理读下降不明显。这个现象通常发生在Segments热点没有真正解决的场景。比如被优化的SQL是某条高频小查询但Top Segment里那些物理读可能来自另一些中低频但扫描量极大的SQL只优化按执行次数排序的前几名反而漏了大头。我现在的习惯是结合Logical Reads和Physical Reads两个维度排Top Segment和Top SQL既要看执行频率高的也要看单次物理读量大的两边都要兼顾。4.3 一个小脚本把均值背后的分布拉出来最后分享一个我常用的辅助SQL专门用来查看某个I/O类等待事件在历史快照里的等待时间分布。AWR报告只给你一个平均值但dba_hist_event_histogram里存了等待时间分桶的数据能还原出大量等待落在哪个区间。-- 查询指定快照区间内db file sequential read 的等待时间分布 select to_char(begin_interval_time, yyyy-mm-dd hh24:mi) as snap_time, n.wait_time_milli, n.wait_count from dba_hist_event_histogram n join dba_hist_snapshot s on s.snap_id n.snap_id and s.instance_number n.instance_number where n.event_name db file sequential read and n.snap_id between 1001 and 1010 order by n.snap_id, n.wait_time_milli;dba_hist_event_histogram里的wait_time_milli字段是按照时间桶来存储的比如0、1、2、4、8、16、32这种递增区间wait_count表示落在该区间内的等待次数。用这个查询你可以直观看到大量的等待是集中在1到4毫秒的低延迟区间还是大量堆积在16毫秒以上的高延迟区间。前者基本可以判定为I/O请求次数过多的问题后者则是存储性能出现问题的最直接证据。这比单纯看AWR均值要可靠得多也是我遇到存争议时用来拍板的核心依据。我个人在实际排查中的体会是AWR的I/O分析与其说是一门技术不如说是一套组合拳。目光只放在任何一个单独指标上都很容易被带偏把顶层等待事件、I/O量级、数据文件延迟、段热点和SQL访问路径串起来看才能做到既不开错药方也不会漏掉背后的病根。每次做分析我都习惯在报告上按负载类型 → 等待类型 → 文件层 → 段层 → SQL层 → OS交叉验证这个顺序做一次完整推演。也建议你下次拿到一份AWR别急着问别人这个db file sequential read高怎么办先自己走一遍这篇文章的路径多数时候答案已经在报告里等着你了。