半夜两点手机连着响了三声。不用看也知道监控又报警了。登录服务器一看CPU使用率拉成一条直线100%已经顶了十来分钟。这个场景做DBA的基本都遇到过一边是业务在群里催一边是OS层面top刷出来一堆占用CPU的进程号可就是不知道到底是哪条SQL把数据库跑死的。这时候AWR报告就是我第一个要捞的东西。AWR全称Automatic Workload RepositoryOracle自带的性能仓库它会定期把数据库内部的工作负载快照存下来。所谓CPU分析就是从这些快照里找出CPU时间到底消耗在哪里、是哪条SQL吃掉了CPU、为什么它会吃掉这么多CPU。这篇文章就围绕AWR里的CPU相关指标和定位方法把我平时排查线上CPU飙升的完整套路拆开讲一遍从指标解读到SQL定位再到真实案例一步步走完整个流程。不管是刚接手数据库运维的新人还是已经写过不少AWR报告的老手只要遇到过“CPU使用率100%但不知道是谁干的”这种问题这篇文章都值得看完。我会把每个指标背后的含义、每个判断的逻辑、每个操作的意图都讲明白而不是丢给你一张标准报告模板让你自己去猜。1. AWR CPU分析的核心先搞懂CPU时间都花在哪了AWR报告拿到手不要急着去翻Top SQL先把最前面的Summary部分看明白。CPU分析的第一步不是找哪条SQL最慢而是先搞清数据库整体处于什么状态——到底是CPU真的不够用还是大量时间都耗在等待上只是表面看起来像CPU问题。方向判断错了后面全白做。1.1 DB Time、CPU Time和等待时间的关系AWR报告首页会给出两个关键数字Elapsed Time和DB Time。Elapsed Time是快照区间的时间跨度比如一小时DB Time则是所有前台会话在数据库内部花费的总时间。这里有个基础换算关系DB Time CPU Time Wait Time非空闲等待时间打个比方你就明白了。一家餐厅有10个厨师相当于CPU数量营业了1小时Elapsed Time后厨实际投入的总工时是30小时DB Time。30小时工时分成了两类真正在切菜炒菜的时间CPU Time以及等食材、等炉灶、等洗碗工的时间Wait Time。如果30小时里有25小时都在切菜炒菜那说明厨师团队已经满负荷问题出在“干活能力”上如果25小时都在等食材那就算厨师再多菜也出不来。所以拿到AWR后我通常先做两个快速判断如果DB Time / Elapsed Time接近甚至超过CPU核数说明数据库确实在满负荷运转CPU是瓶颈的概率很大。如果CPU Time占DB Time的比例很高比如超过70%基本可以断定这是一个CPU密集型问题接下来重点查SQL如果比例很低说明大部分时间在等待CPU飙升只是表象得往锁、I/O、内存方向排查。注意这里的CPU Time是数据库会话消耗的CPU时间和OS层面看到的CPU使用率不是一回事。OS的CPU使用率还包括后台进程、其他非数据库应用的开销。AWR头部还有一行Instance CPU %表示数据库实例消耗的CPU占操作系统总CPU的比例。如果这个比例很高比如90%以上说明CPU基本是被数据库吃掉的可以锁定方向如果只有30%那得先看看OS上还有别的什么进程在抢CPU。1.2 AWR里需要优先看的CPU相关指标AWR报告的Summary部分有张Load Profile表里面每秒钟的事务量、逻辑读、物理读、执行次数等指标能帮你快速感知系统的负载形态。但做CPU分析我更关注的是下面这几个指标的组合。第一个是CPU used by this session。这个指标在Instance Activity Stats部分表示所有会话消耗的CPU总时间。把它和DB Time放在一起看能算出CPU等待的占比。第二个是DB CPU在Time Model Statistics部分这是数据库内核自己统计的CPU消耗也是判断CPU瓶颈的最核心数据。第三个是Load Average。AWR报告的头部会列出快照开始和结束时的系统负载。这个值代表操作系统运行队列中的平均进程数如果它持续超过CPU核数说明已经有进程在排队等CPU了。我见过不少案例数据库内部DB CPU并不高但Load Average非常高最后发现是OS层面有别的进程把CPU抢光了AWR里根本看不出来。还有一组容易被忽略的指标是Parse Time Elapsed和Hard Parse次数。如果CPU used by this session很高但Top SQL里的单条SQL看起来都不算离谱那么很可能是每秒大量的硬解析在消耗CPU。这种CPU消耗是“碎”的分摊到每条SQL上都不显眼总量却非常惊人。判断方法就是看Load Profile里的Parses和Hard Parses每秒钟的次数如果硬解析每秒几百上千次那基本可以断定CPU都烧在解析上了。-- 通过DBA_HIST_SYSMETRIC_HISTORY查历史CPU指标示例 SELECT begin_time, ROUND(value, 2) AS cpu_usage_pct FROM dba_hist_sysmetric_history WHERE metric_name CPU Usage Per Sec AND begin_time SYSDATE - 1 ORDER BY begin_time;实际执行的时候上面的SQL可以查出过去24小时CPU使用率的曲线结合告警时间点能快速确认CPU飙升是从哪个时刻开始的再和AWR快照区间对齐看那一小时的报告才有针对性。2. 定位高CPU消耗的SQLAWR报告中的SQL部分怎么看判断完方向确认是CPU密集型问题之后下一步就是找到具体是哪条SQL在烧CPU。Oracle的AWR报告里SQL部分有大量排序其中SQL ordered by CPU Time就是专门为CPU分析准备的。但这块也有不少坑排序方式选不对、单次和总量分不清很容易把排查方向带偏。2.1 分清“总量CPU”和“单次CPU”打开AWR的SQL部分你会看到两个表格并排SQL ordered by CPU Time和SQL ordered by CPU Time (Avg)。名字看起来差不多但含义完全不同排查的时候必须两个一起看。按总额排的SQL ordered by CPU Time反映的是快照区间内这条SQL所有执行次数消耗的CPU总和。总额高有两种可能单次执行就非常耗CPU或者单次还好但被调用了成千上万次。按平均排的SQL ordered by CPU Time (Avg)反映的是每一次执行平均消耗多少CPU。平均高说明这条SQL本身质量差每一次执行都做大量计算。我在实际排查时有个习惯如果总额排第一的SQL平均CPU也排在前列那这条SQL就是元凶性能和写法都有问题如果总额排第一但平均CPU很低那说明不是SQL写得差而是被“疯狂调用”常见原因包括应用层循环调用、N1查询、或者是夜间批处理集中跑数。这里还有一个非常经典的翻车场景AWR里排名第一的SQL占了60%的CPU但这是一条BEGIN DBMS_STATS.GATHER_TABLE_STATS(...); END;或者DELETE FROM ... WHERE ROWNUM 1000这种后台自动任务。很多新手看到Top SQL里有SQL就急着优化结果改了半天发现根本不是业务SQL。所以定位到SQL之后第一件事是确认这条SQL的来源SQL Module字段和类型别把系统维护任务当成业务问题处理。2.2 结合执行计划判断SQL为什么烧CPU只看AWR里的SQL文本是远远不够的CPU高一定有原因。常见的有三类全表扫描加大量行处理、排序或哈希操作占用内存和CPU、执行计划走了错的索引导致读取放大。要判断是哪一类必须把这条SQL的执行计划拉出来看。AWR本身不直接显示执行计划但可以通过SQL ID从历史库里取。操作很简单-- 从AWR快照中捞SQL的完整执行计划 SELECT * FROM TABLE (DBMS_XPLAN.DISPLAY_AWR(你的SQL_ID, NULL, NULL, ALL));拿到执行计划后重点看几个地方TABLE ACCESS FULL出现在大表上SORT ORDER BY或HASH JOIN对应的Bytes和Rows估算是否夸张Cardinality基数估计和实际行数偏差是否超过百倍。只要看到这些基本就能判断出CPU消耗的来源了。举个例子。之前排查过一条统计报表SQLAWR显示它单次执行要消耗约12秒CPU每天定时跑一次虽然只跑一次但跑的时候整个库的CPU直接飙到80%以上。执行计划拉出来一看问题非常清楚主表三千万行过滤条件里的status列没有索引执行计划走了全表扫描然后嵌套循环去关联另外两张表每关联一次都要全表扫一遍。这种SQL的CPU消耗主要在建行上。评估行数几百行实际返回几十万行优化器低估了基数选错了join方式。这种案例在AWR里特别典型SQL本身的逻辑没错但执行计划错了CPU就全烧在无谓的数据搬运上了。2.3 从AWR历史库直接捞SQL的实操方法除了看报告里现成的Top SQL我经常还会用AWR的底层表DBA_HIST_SQLSTAT直接查。原因很简单报告里的Top N可能被截断尤其当问题SQL的排名在第10名开外时AWR默认只展示前10到20条你光看报告是发现不了它的。而直接查历史表可以把时间范围扩大、排序方式自定义不会漏。下面这条SQL是我常用的通过它可以在任意两个快照ID之间按CPU总消耗找出Top SQLSELECT sql_id, SUM (cpu_time_delta) / 1000000 AS cpu_sec_total, SUM (executions_delta) AS exec_total, SUM (cpu_time_delta) / 1000000 / NULLIF (SUM (executions_delta), 0) AS cpu_sec_per_exec FROM (SELECT sql_id, cpu_time_delta, executions_delta FROM dba_hist_sqlstat WHERE snap_id BETWEEN 100 AND 120) GROUP BY sql_id ORDER BY cpu_sec_total DESC;cpu_time_delta字段存的是两次快照之间CPU时间的增量单位是微秒所以除以1000000换算成秒。executions_delta是执行次数增量。这条SQL跑出来的结果比AWR报告里的Top SQL更全也更能灵活对比不同时间段的CPU消耗变化。3. 实战案例一次线上CPU 100%的完整排查过程前面的方法论说了一堆接下来用一次真实场景串一遍。这套流程是我目前在用的标准动作从接到告警到定位根因用AWR把排查时间控制在半小时以内完全不夸张。3.1 现象与第一反应不要急着杀会话某天下午线上系统告警应用响应变慢核心交易接口的耗时从50ms涨到3秒。登录服务器执行top看到CPU使用率已经100%数据库所在主机上有好几个oracle进程的CPU占用在100%以上。这时候最忌讳的就是直接kill session。虽然杀掉会话能让CPU立刻掉下来但问题SQL还在重启之后还会继续跑而且如果杀掉的会话正在回滚回滚本身也要消耗CPU和I/O反而可能让情况更差。正确做法是先收集证据再动手。我当时的操作顺序是这样的用top -H -p oracle_pid查看数据库后台进程的线程确认CPU集中在哪些进程上。查询GV$SESSION和GV$SQL把PID和SQL_ID对应起来。快速抓取当前正在执行的SQL文本先看一眼是什么类型的语句。然后才开始生成AWR报告准备深入分析。生成AWR报告用Oracle自带的脚本sqlplus / as sysdba ?/rdbms/admin/awrrpt.sql按提示输入报告类型html或text、快照天数、起始和结束快照ID。通常选text格式因为方便直接复制关键段落。注意选快照区间时要让区间覆盖CPU飙升时段但如果CPU已经持续高了一小时我会选前后两个快照一个是飙升前的一个是飙升中的做对比分析。3.2 AWR报告解读从指标到SQL一步步缩小范围那份AWR报告拿到的第一时间我先看的是Report Summary里的几行数字Elapsed Time: 60.03 (mins) DB Time: 2,456.17 (mins)DB Time除以Elapsed Time约等于40.9数据库服务器是32核40.9已经超过了32说明数据库的工作负载已经超出了CPU的供给能力整个数据库处于饱和状态。继续看Time Model Statistics结果显示DB CPU占DB Time的91.3%等待时间占比不到10%。到这里判断已经很明确了这就是一个典型的CPU瓶颈问题不是I/O、不是锁等待。接下来进入SQL部分SQL ordered by CPU Time排名第一的SQL_IDCPU总消耗占整个报告的43.7%文本是一条UPDATE语句看Modul字段来自一个Java应用。单次CPU消耗其实不高只有0.02秒但快照区间内执行了接近50万次。这就是典型的“小SQL高并发”模式平均值看着无害总量却能烧掉半个数据库的CPU。顺着这条SQL拉出执行计划问题立刻暴露WHERE条件里的order_status是普通索引选择性其实还不错但UPDATE语句里还有个AND update_time ...条件这个字段没有跟order_status组成复合索引。优化器选择了索引回表每一条匹配记录都要回表读取一次完整的数据行再过滤update_time。回表次数一多CPU全花在一行一行的数据读取和过滤上了。再仔细看另一个数据报告Load Profile部分Execute to Parse %只有40%多说明应用没有使用绑定变量每条SQL都在重复硬解析。这部分额外消耗的CPU虽然没被算进Top SQL里但在总量上又一次推高了系统负载。3.3 优化方案落地与效果验证排查做完了改动分两步进行第一步紧急降载。应用当时还在持续跑CPU顶在100%我先通过ALTER SYSTEM KILL SESSION定向杀掉了几个消耗最高的会话来缓解但这只是止血撑不了太久。同时联系应用团队让这个接口的并发调用先降速给数据库留出喘息时间。第二步SQL优化。定位到那条UPDATE语句后我加了一个复合索引CREATE INDEX idx_order_status_time ON order_table (order_status, update_time);索引建好后执行计划从“索引回表过滤”变成了“索引范围扫描直接命中全部条件”。单次CPU消耗从0.02秒降到0.003秒左右执行次数不变的情况下CPU总消耗直接掉了85%。硬解析的问题也一起处理了应用层启用绑定变量Execute to Parse %从40%恢复到98%以上。优化结果在第二天的AWR报告里得到了验证。峰值时段的CPU Usage Per Sec从原来的接近100%降到30%左右Top SQL里那条UPDATE语句的CPU占用已经跌出前五。系统整体响应时间也恢复到正常水平。4. CPU分析避坑指南常见误区与速查表AWR的CPU分析做了这么多次我自己踩过的坑不算少有些问题反复出现这里整理成一份避坑指南希望能帮你少走弯路。4.1 新手最容易犯的几个判断错误第一个误区是把OS的CPU使用率和数据库CPU时间混为一谈。OS的top显示100%不代表数据库的DB CPU就一定是100%。系统里可能有备份进程、监控采集程序、日志同步工具在抢CPU尤其那些部署了多个应用实例的机器oracle进程只是其中之一。所以判断问题的时候先看AWR里Instance CPU %这个指标如果它只有30%那就说明数据库不是CPU飙升的元凶得去OS层面找别的进程。第二个误区是只看Top SQL的CPU总额忽略了执行计划的变化。同一个SQL昨天CPU消耗1秒今天变成5秒SQL文本一个字没改那问题一定出在执行计划变了。AWR的SQL部分记录了Plan Hash Value对比两个快照里同一SQL_ID的Plan Hash Value就能发现计划是否发生了变化。如果变了下一步就去对比新旧计划看是不是统计信息过期导致基数估计错误。第三个误区是不看等待事件直接下结论。Top 5事件里如果CPU time排在第二位但第一位是log file sync那CPU高很可能是大量频繁提交导致的连锁反应而不是CPU本身不够。这时候直接优化SQL不一定有效得先解决提交频率和日志写入效率的问题。4.2 紧急场景下的快速处置手段如果线上数据库CPU已经100%顶了一大会儿业务基本不可用了等AWR慢慢分析是来不及的。这种情况下我会按下面的顺序快速止血同时并行做AWR分析先执行SELECT sid, serial#, sql_id, cpu_time FROM v$session WHERE statusACTIVE AND typeUSER ORDER BY cpu_time DESC;找出当前消耗CPU最多的活动会话。确认是业务SQL后优先和应用团队确认是否可以取消或降并发而不是直接kill。如果应用无法快速响应再考虑ALTER SYSTEM KILL SESSION sid,serial# IMMEDIATE。如果高CPU是由并行查询引起的可以暂时调低parallel_max_servers限制并行度。CPU被并行进程吃满的场景很常见尤其数据仓库环境。如果是由于缺乏索引导致的重复全表扫描在风险可控的前提下快速补索引是最有效的一招。注意KILL SESSION要谨慎用于正在跑大事务的会话它会导致回滚回滚期间CPU和I/O照样高。所以kill之前先用V$TRANSACTION查一下这个会话是否有未提交的事务事务多大再决定是否动手。4.3 常见问题速查表场景优先查看AWR的位置判断逻辑典型处理CPU使用率100%DB CPU占比高Time Model的DB CPU数据库自身消耗CPU进入SQL分析定位Top SQL并优化CPU使用率100%Instance CPU%不高头部Instance CPU%数据库之外有其他进程占CPUOS层面排查其他进程Top SQL都是小SQL单次CPU低SQL ordered by CPU Time总额高并发小SQL累积查绑定变量、应用降并发单条SQL平均CPU高SQL ordered by CPU Time (Avg)单次执行质量差分析执行计划、加索引Load Average高但DB CPU不高头部Load Average运行队列堆积等待事件多查等待事件、上下文切换Top 5事件里等待类排前面Top 5 Timed Events不是纯CPU瓶颈是等待链按首位等待事件处理同一SQL执行计划变化SQL部分Plan Hash Value对比统计信息或绑定变量导致重新收集统计信息、固定计划这个表基本覆盖了我遇到的大部分CPU排查场景。实际操作中你可以先看现象对应到某一行再按那一行的判断逻辑往下走比从头到尾读一遍AWR报告高效得多。做AWR CPU分析这几年我最深的体会是AWR报告不是一个“看一遍就能下结论”的东西它更像一张地图告诉你在某个时间段里系统内部发生了什么事但具体是哪里出了毛病还是得靠你自己顺着线索一步步往深处挖。CPU分析尤其如此从DB Time到Top Event从Top Event到Top SQL从Top SQL到执行计划每深入一层定位就精准一分。这篇文章里整理的流程和脚本都是我在真实故障中反复用过、验证过的东西希望你下次遇到线上CPU告警的时候能少一点手忙脚乱多一点胸有成竹。