Oracle性能诊断实战:从锁等待到SQL优化的完整排查思路
1. 性能诊断不是看一堆报告,而是先搞清楚"用户到底卡在哪"
做Oracle数据库运维这些年,我接到过太多类似的工单:应用那边说"系统变慢了",开发说"SQL没问题",领导说"赶紧处理"。但真要定位,你会发现每个人嘴里的"慢"根本不是一回事。有人是某个查询报表转圈圈,有人是核心交易接口超时,还有人只是感觉"最近都不太对劲"。如果一上来就闷头抓取AWR报告、翻等待事件,大概率会在错误的方向上浪费大半天。
在FGMT12这一期里,我想把Oracle性能诊断的整体思路重新捋一遍。诊断性能问题,本质上是在回答三个问题:用户感受到的慢是什么慢?慢的环节在哪个层面(应用、网络、数据库)?数据库内部又是哪个部分在拖后腿?只有把这三层想清楚,后面的所有命令、脚本、报告才有意义。
我习惯把性能诊断分成三个层次:现象层,也就是业务反馈的具体表现,比如某支交易耗时从200毫秒涨到2秒;系统层,包括主机CPU、内存、磁盘I/O、网络;数据库层,涉及等待事件、SQL执行计划、锁与并发、日志与归档等。三层的诊断手段完全不同,但必须串联起来看。比如你发现数据库里某个SQL跑了很久,如果不去看主机层的I/O延迟,你很难判断是该加索引,还是该换存储,或者该调整并行度。
这篇文章我会直接从一次真实的生产性能问题入手,讲清楚从现象到根因的完整排查链路,再把AWR报告怎么读、等待事件怎么看、SQL怎么分析、统计信息与执行计划怎么验证这些基础功拆开讲透。最后聊聊优化做完之后怎么验证效果,以及那些容易让人栽跟头的边界条件。适合刚入门Oracle运维的DBA,也适合被性能问题折腾过几次、想建立系统排查思路的开发同学。
2. 一次真实故障的完整排查链路:从"页面转圈"到"锁等待"
2.1 现象层:业务方描述的矛盾信息
那天下午三点多,业务方在群里喊了一句"系统好卡",然后就没了下文。等我去追问细节,得到的回复是"页面一直在转圈,有时候能打开,有时候干脆超时"。这种描述对DBA来说几乎等于什么都没说,但我很清楚,我不能让这种模糊状态持续下去。
我先做了一件事:把"卡"拆成可量化的指标。我让业务方帮忙确认三个信息:具体是哪个功能模块、操作哪个菜单或按钮、大概几点开始出现、有没有试过刷新之后能否恢复。等了十来分钟,反馈回来说是个查询报表页面,点开之后结果集加载很慢,以前大概三秒内出数据,现在经常要一分钟以上,偶尔直接报ORA-01013(用户取消操作)之类的错误。
这一步看着不起眼,实际上决定后续排查方向。如果问题出在写入操作上,优先怀疑锁竞争和回滚段;如果问题集中在某个查询页面上,重点就落到SQL执行效率和执行计划变化上。有了这个方向,我才开始看系统层。
2.2 系统层:排除主机资源瓶颈
我先登录数据库服务器,查了CPU、内存和负载情况。用top命令看到CPU整体使用率才30%左右,负载也不算高,内存没有出现swap,磁盘I/O的iostat显示util和await都算正常。这个结果其实已经告诉我:问题大概率不在主机资源不够用上,数据库层面的阻塞或某个SQL自身出了问题才是重点。
为了进一步排除网络因素,我让业务方直接连到数据库执行那条报表SQL,绕过应用中间层,看看纯数据库环境下需要多久。测试下来,手动执行也慢,这就把中间件、连接池和网络延迟的影响剥离了。排查范围进一步收窄到数据库内部。
接下来我在数据库里查了当前会话在干什么。用v$session关联v$sqlarea看正在执行的SQL,结果发现报表对应的SQL并没有在跑,倒是有好几个会话卡在enq: TX - row lock contention这个等待事件上。看到这个事件,我心里大概有数了:问题不是这条报表SQL本身执行计划突然变差,而是它被别的事务堵住了,拿不到它需要修改或读取的那行数据的锁。
2.3 数据库层:顺着锁等待链揪出源头
enq: TX - row lock contention的典型场景,就是一个会话更新了某行数据但没提交,另一个会话想去更新同一行,只能干等。要找到谁锁了谁,需要查v$lock和v$session。
我当时用了一条很经典的查询,把阻塞者(blocker)和被阻塞者(waiter)关联出来:
结果显示,有将近十个会话的blocking_session都指向同一个会话。再去看那个阻塞会话到底在执行什么SQL,发现是一条针对业务订单表的UPDATE语句,已经跑了四十多分钟没提交。顺着这个SQL的sql_id去查它的执行计划和执行历史,发现它之前正常的时候几秒就能跑完,现在突然变慢,是因为它执行计划里走的索引路径变了,优化器选择了一个低效的嵌套循环,每秒处理的行数少得可怜。
到这里,根因链条基本清晰了:某个批量更新任务因为执行计划劣化,持锁时间暴涨,把后面依赖同一批数据的报表查询全部堵死。如果当时只盯着那条报表SQL,加再多索引也解决不了问题,因为报表SQL本身并没有被改过执行计划,它是被锁牵连的受害者。
2.4 处理过程:先救火,再治本
现场处理分两步。第一步先"救火":和业务方确认阻塞会话对应的批量更新任务能否中断,确认可以之后,我直接ALTER SYSTEM KILL SESSION把阻塞会话干掉,让后面堆积的会话马上恢复。报表页面在数秒内恢复可用,业务方反馈正常。
第二步"治本":那条批量UPDATE的执行计划为什么劣化,需要进一步分析。我先收集了相关表的统计信息,然后重新执行那条更新SQL,观察执行计划是否恢复。结果发现,优化器依然选择了低效的路径,说明统计信息不是唯一原因。继续看表结构,发现关联条件列的数据分布严重倾斜,某几个值占了绝大部分数据,而直方图信息又是旧的。重新收集带直方图的统计信息后,执行计划恢复正常。
为了彻底规避这类问题,我还做了两件长期的事:一是给核心批量更新任务加了一个monitoring提示并设置合理的parallel参数,避免优化器再次抽风;二是和开发约定,批处理任务尽量避开业务高峰期,分批提交,减少单次持锁时间。这类规范看起来简单,实际能挡掉大部分锁等待故障。
3. AWR报告的阅读逻辑:不是看红绿灯,而是看趋势变化
3.1 两个时间段必须对比,单看快照毫无意义
很多初学者拿到AWR报告,第一反应是看报告顶部的"红绿灯"指标,比如DB Time、Elapsed Time、Load Profile里的每秒事务数。如果看到DB Time比Elapsed Time高很多,就知道数据库确实忙;但如果只看单个快照区间的绝对值,你根本判断不了这是正常负载还是故障状态。
正确姿势是取问题时间段和同期正常时间段的AWR做对比。比如这次故障发生在下午三点到三点半,那就找前一天的同一时间段,以及当天上午的同小时段,拉出三份报告对比。重点看几个关键指标的变化幅度:DB Time。如果从每天的同时段的2000秒涨到现在的8000秒,那就是实打实的负载异常;每秒事务数可能有小幅波动,但总体维持在一个量级;物理读/逻辑读的比例变化,往往能暗示执行计划是否劣化或数据量是否暴增。
我在实践中习惯先把Load Profile里的DB CPU、Physical Read Total Requests、User Transaction这几个值记录下来,再去看Top 10 Foreground Wait Events,最后才翻SQL统计部分。这个顺序其实是从宏观到微观的递减过程:先确认数据库整体忙不忙,再确认忙在什么地方,最后锁定是哪些SQL在制造这些等待。
3.2 Top等待事件的常见分类与真实含义
AWR报告里的Top 10 Foreground Wait Events是最直观的问题入口。我一般把等待事件粗分成几类:
| 等待事件类别 | 常见事件 | 通常对应的优化方向 |
|---|---|---|
| I/O类 | db file sequential read、db file scattered read、direct path read | SQL执行计划、索引设计、存储性能 |
| 并发/锁类 | enq: TX - row lock contention、enq: TX - index contention、library cache lock | 业务事务设计、索引顺序、热块争用 |
| CPU类 | log file parallel write、CPU used by this session | 日志磁盘性能、整体CPU资源 |
| 空闲/配置类 | SQL*Net message from client、job queue slave wait | 多半是正常等待,但要确认是否有网络异常 |
看到db file sequential read高,不要条件反射式地觉得"该加索引了"。这个等待事件在单块读场景下是正常的,关键是看它是不是伴随着大量低效的单块读。比如一个SQL本该走索引快速定位,结果因为索引选择性差,导致大量回表,单块读次数飙升。这时候加索引没有用,该做的是修正执行计划里的索引选择,或者改用覆盖索引减少回表。
再比如enq: TX - index contention,这个事件经常出现在大量并发插入且索引值单调递增的场景。比如按序列生成的ID做索引,所有会话都往索引的最右侧插入,热点块争用严重。解决方案通常是把索引改成反序索引,或者改用哈希分区来分散热点,这在物联网、流水表场景里非常常见。
3.3 SQL Statistics部分怎么筛选真正需要优化的SQL
AWR报告里会列出多个维度的SQL排名,包括Elapsed Time、CPU Time、Executions、Buffer Gets、Physical Reads等。我筛选待优化SQL时的原则很简单:
- 看 Elapsed Time 总和:如果某条SQL占了整个快照区间DB Time的大头,优先看它。
- 看单次执行代价:
Buffer Gets/Executions这个比值很重要。如果一条SQL执行了几万次,每次只读几十个块,那它未必比一条执行一次但读了几百万块的SQL更需要优化。后者往往是全表扫描或执行计划劣化导致。 - 看执行次数变化:如果
Executions暴增,比如从一分钟几次涨到几千次,N+1查询或应用循环调用就是嫌疑点。
我的习惯是拿前20条耗时最高的SQL,逐条看它的执行计划、涉及的表、索引情况和统计信息新鲜度。不要贪多,一次性能优化只盯住一两条最有价值的SQL,改完验证,再处理下一条。贪多嚼不烂,而且同时改多个点出了问题根本没法回退判断。
4. SQL分析的核心要素:执行计划、统计信息、索引设计一个都不能少
4.1 拿到慢SQL之后,第一步不是加索引
我见过太多人拿到一条慢SQL,瞄一眼WHERE条件,立刻就说"这里缺索引,加上就好了"。这种思路在一些简单场景下碰巧有效,但遇到复杂查询往往会把坑挖得更深。正确顺序是:先拿到这条SQL真实的执行计划,再看它每一步扫描了多少行、返回了多少行,然后判断瓶颈是统计信息失真、索引缺失、SQL写法还是数据分布问题。
我常用的看执行计划方式有两种。一种是直接在会话里执行EXPLAIN PLAN FOR语句,然后查DBMS_XPLAN.DISPLAY,这种方式适合快速预览优化器选定的路径;另一种是看DBMS_XPLAN.DISPLAY_CURSOR,拿到SQL真实执行时的计划,还能看到实际的A-Rows(实际行数)和A-Time(实际时间)。两种方式的差异很关键,因为EXPLAIN PLAN展示的是优化器"预计"要走的路径,而DISPLAY_CURSOR展示的是真实执行的路径,两者经常不一致。
看执行计划时,我的关注点按顺序来:
- 查看
Id列的缩进结构,找到最深的几行,那通常是表访问或连接操作的起点。 - 看每步的
Rows和Bytes估算值与实际值是否出入巨大。如果估算100行实际扫了100万行,统计信息大概率过期或采样不准。 - 看有没有意外的
TABLE ACCESS FULL、SORT ORDER BY、HASH JOIN后的超大Cardinality。
4.2 统计信息过期:很多"灵异"性能问题的真凶
统计信息的问题非常隐蔽,因为它不会报错,只会让优化器"自信地选择错误路径"。最典型的场景就是:一张表的数据量从几万行增长到几千万行,但统计信息还停留在几万行时的状态。优化器会以为走索引回表很便宜,结果实际是全表扫描加海量回表,慢得离谱。
处理办法有三个层次:
- 日常预防:给表设置合理的自动统计信息收集策略,比如
DBMS_STATS.GATHER_TABLE_STATS配合auto采样,并保留历史统计信息供回退。 - 发现问题时:手动重新收集统计信息,收集时带上直方图,比如
DBMS_STATS.GATHER_TABLE_STATS('SCOTT','ORDERS', METHOD_OPT => 'FOR ALL COLUMNS SIZE AUTO')。注意生产环境高峰期谨慎操作,收集统计信息本身也有代价。 - 收集完仍无效:说明不是统计信息新鲜度问题,可能是数据分布极端或SQL写法本身不适合当前优化器版本,需要进一步做执行计划固化(SQL Plan Baseline/SPM),或者改写SQL。
我记得有一次,一条按订单时间查询的分区表SQL,夜间跑批突然从10分钟变成2小时。查下来发现分区裁剪失效,优化器扫描了所有分区。重新收集统计信息之后,分区裁剪恢复,跑批时间回到10分钟。后来查原因,是新上的数据清理任务把统计信息表里的分区数据搞乱了。这个案例也提醒我,排查性能问题时,不仅要看SQL本身,还要想有没有什么后台任务动过统计信息、动过表结构、动过索引状态。
4.3 索引设计:多列索引、函数索引和索引失效的坑
关于索引设计,大家基本都知道B树索引适合等值查询和范围查询,但具体到实际场景,仍然有不少容易踩的细节。
多列索引的列顺序是个老生常谈但永远有人搞错的问题。原则是:区分度高的列放前面,经常用于等值过滤的列放前面。但要注意,这不是绝对的,比如一个组合索引(A, B),如果查询条件里只有B没有A,这个索引大概率用不上。有些人喜欢给每一列都建单列索引,期望数据库自动合并索引,实际上在Oracle里这种方式远不如一个组合索引高效,还会拖慢DML。
函数索引这个点,很多开发同学容易栽跟头。比如查询条件写WHERE TRUNC(create_time) = TRUNC(SYSDATE),如果create_time字段上有普通索引,优化器根本无法使用,因为列被函数包裹后索引无法匹配。解决方式是建一个函数索引CREATE INDEX idx_trunc_ct ON t(TRUNC(create_time)),或者在应用层把查询改写成范围条件WHERE create_time >= TRUNC(SYSDATE) AND create_time < TRUNC(SYSDATE) + 1。后者更推荐,因为函数索引会让插入和更新需要额外维护索引,而范围改写既走普通索引,又保留字段本身的统计信息可利用性。
索引失效的情况也值得警惕。最常见的两类:一是索引列上做了隐式类型转换,比如字符字段WHERE char_col = 123,Oracle会把索引列隐式转成数字,导致索引失效;二是表上大量DML操作之后索引碎片化严重,索引高度增加,扫描效率下降。定期重建索引、监测索引状态(STATUS字段是否为VALID)、关注索引的BLEVEL和CLUF,是DBA的日常功课。
5. 一个典型优化实战:从trunc(sysdate)说到多表关联UPDATE
为了把前面的方法串起来,我用一个精简但典型的案例来演示。假设有张订单流水表orders_history,数据量在千万级,按天分区。业务需要统计今天所有有效订单的总额。
5.1 第一版SQL:函数让索引报废
业务方最初写的查询是:
执行计划显示,分区裁剪只裁剪了一部分,create_time列上的索引完全没有被使用,扫描方式变成了全分区扫描加过滤。原因就是TRUNC(create_time)把列包装了,优化器即便能看到索引也没有办法基于函数做范围扫描。
改写后的SQL:
这次执行计划走了create_time上的索引范围扫描,实际扫描的行数从千万级降到百万级以内,查询时间从原来的几十秒降到几百毫秒。这个例子看着简单,但我在实际工作中遇到太多类似写法,改完之后基本立竿见影。
5.2 第二类问题:多表关联UPDATE的更新优化
还有一个高频问题是多表关联UPDATE。很多开发喜欢写成子查询嵌套或者关联更新:
这种写法在数据量小的时候没问题,但一旦order_head和order_detail都上了百万行,子查询反复执行,代价非常高。更高效的做法是先用MERGE做关联更新,或者改用可更新的内联视图:
MERGE的优势在于只扫描一次源表和目标表,做一次连接,然后集中更新,比逐行子查询高效得多。我优化过的一个类似场景,原来用子查询UPDATE跑了两小时没跑完,改成MERGE后十分钟完成。差距就是这么明显。
5.3 大分页查询:rownum翻页的性能深坑
热搜词列表里有"oracle分页",这也是个讨论度很高的问题。Oracle经典分页写法是:
问题在于,ORDER BY create_time DESC这层会把全表数据都取出来排序,然后才应用ROWNUM过滤,翻页越深,排序代价越大。优化思路通常是:在ORDER BY字段和WHERE条件上创建合适的索引,让排序尽量走索引有序扫描,避免大排序;或者用FETCH FIRST ? ROWS ONLY的12c+语法,优化器处理这类分页更聪明。更深层的做法是按业务需求缩短分页深度,因为用户一般只看前几十页,而后面的页面本身就是低价值的。
6. 其他高频故障的快速定位思路:监听、ORA-28547与12c环境
6.1 监听服务无法启动的处理顺序
"oracle监听服务无法启动"这个话题在热搜里热度极高,我简单说下排查顺序。起因多半是三种:监听配置文件listener.ora语法错误、端口被占用、Oracle用户权限或环境变量问题。遇到监听起不来,我第一步是看$ORACLE_HOME/network/log目录下的监听日志,通常会有明确的报错信息。常见的是TNS-12541: TNS:no listener或TNS-12560: TNS:protocol adapter error。
端口被占用场景处理起来比较简单,用netstat -ano | findstr 1521(Linux用ss -lntp | grep 1521)找到占用进程,杀掉或改监听端口。如果listener.ora配置语法有问题,可以用lsnrctl status、lsnrctl reload来验证,错误信息会直接告诉你哪一行有问题。还有一类场景是Windows服务里Oracle监听服务启动之后秒退,多半是Oracle用户密码或者服务依赖项问题,重新配置服务或者检查ORACLE_HOME环境变量即可。
6.2 ORA-28547:这个报错常被误判为监听问题
ORA-28547: connection to server failed, probable Oracle Net admin error 是热搜里的高频词。这个问题我在论坛里回复过很多次,它本身不是监听服务挂掉,而是sqlnet.ora或tnsnames.ora的配置与服务器端不一致。
最常见的根因是SQLNET.AUTHENTICATION_SERVICES的设置问题,比如服务器端只允许NTS认证,而客户端配置了NONE或没有正确配置;或者sqlnet.ora里设置了TCP.VALIDNODE_CHECKING,把客户端IP加入黑名单了。处理思路很简单:对比客户端和服务器端的sqlnet.ora参数,把差异项拉平,尤其检查认证服务和IP白名单/黑名单配置。注意SQLNET.AUTHENTICATION_SERVICES=(NONE)的配置含义,它是让数据库走用户名密码认证,不打NT集成认证,这在某些场景下会引起连接异常。
6.3 12c删除不干净导致安装失败的坑
热搜里有"12c删除不干净+oracle",这个场景多见于Windows环境。Oracle 12c卸载不干净,注册表里有残留条目,再次安装时检测到已存在的Oracle主目录或服务,直接安装失败。
处理这类问题,我的建议是三步走:先走官方卸载程序(deinstall),删除Oracle主目录和监听服务;再手动删除注册表里的Oracle目录条目;最后清理系统环境变量ORACLE_HOME、PATH里的残留路径。如果还是提示"Oracle Net Configuration Assistant无法启动",多半是JDK环境变量冲突,检查JAVA_HOME和PATH里有没有多个Java版本。对于还不打算重装系统的用户,可以试试MicrosoftFixit或手动逐条清理,但这类问题根治起来确实费时,最好在装数据库之前就规划好卸载路径。
7. 统计信息与执行计划的联动验证:不要只信一次执行结果
优化完SQL或收集完统计信息后,很多人喜欢单次执行一遍,看到时间降下来就宣布"搞定"。这种做法在大多数情况下没问题,但偶尔会有误导性——因为单次执行的A-Time包含了物理读冷缓存的开销,第二次执行可能走了Buffer Cache,速度虚高。
我的验证方法是:用DBMS_XPLAN.DISPLAY_CURSOR查看真实执行计划,对比优化前后的A-Rows、A-Time、Buffers与Reads几个关键字段。Buffers代表逻辑读次数,它比单纯看执行耗时更稳定,因为执行耗时受缓存、并发、I/O抖动影响很大,而Buffers基本只取决于执行路径和访问行数。如果Buffers明显下降,执行计划优化几乎是确定的。然后我会再连续执行两到三次,取中间值,排除冷缓存和热缓存的极端情况。
还有一个值得注意的点:执行计划与绑定变量窥探(Bind Peek)。如果SQL里用了绑定变量,Oracle在首次硬解析时会把变量值带入估算,之后即使数据分布变化,执行计划也可能一直沿用首次的。遇到这类问题,可以收集统计信息后做DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO,或使用SPM(SQL Plan Management)固定住正确的计划。SPM是个好东西,但也别滥用,只有在执行计划确实不稳定、且你能确定哪个计划是优的时候才适合固定。
8. 边界条件与常见误区:这些坑我替你踩过
做Oracle性能诊断时间久了,我发现很多事情不是"会不会做",而是"会不会想到去做"。下面这几点,是我踩过坑之后的经验总结。
误区一:并发量大时就认为必须上读写分离或分布式数据库。 实际上很多系统的性能问题只是SQL写法和执行计划的问题,根本不需要动架构。优化一条SQL的成本,远低于迁移一套分布式数据库的成本。在决定引入中间件或分库分表之前,先把AWR和Top SQL分析清楚。
误区二:所有的等待事件都代表故障。 前面提到过,SQL*Net message from client这类事件在等待客户端操作时出现是完全正常的,如果你把空闲等待也算进去,很容易误判。看AWR报告时要分清楚Foreground Wait Events和Background Wait Events,前者直接影响用户响应时间,后者通常和后台进程相关,比如log writer、archive process,需要在独立维度分析。
误区三:加了索引就是万能的。 索引不是越多越好,尤其是OLTP高写入场景,每个索引都会放大插入、更新、删除的开销。我遇到过一张业务表原本只有两个索引,后来被人一口气加到十来个索引,结果查询没变快多少,批量导入反而慢得不行。索引优化的目标是用最少的索引支撑最多的查询,而不是给每条SQL都建一个新索引。
误区四:收集统计信息只看表,不看索引。 统计信息不仅包括表级别(行数、块数),也包括索引级别(高度、集群因子)。重建索引或大批量DML之后,表统计信息和索引统计信息可能失衡。最好的做法是用dbms_stats.gather_index_stats或者直接GATHER_TABLE_STATS带上索引统计收集。很多执行计划异常,其实是因为索引统计信息和实际严重不符。
误区五:性能优化只关注数据库层面,忽略应用端行为。 比如v$session里看到大量SQL*Net more data to client,说明数据库已经在往客户端灌大量数据,问题可能出在应用一次拉取的数据量过大,而不是数据库内部排序慢。再比如高并发下大量连接反复建立和销毁,会增加解析和上下文切换开销,这类问题在数据库里怎么优化SQL都收效有限,必须在应用层引入连接池并合理配置。
9. 性能优化完怎么评估效果:衡量指标与回归策略
优化完成不意味着直接关工单,还要有验证环节。我通常从四个维度衡量优化效果:
业务响应时间,直接来自应用监控或业务方反馈。这是最核心的指标,如果用户感知不到变化,前面做的所有技术分析都需要重新审视。
数据库关键指标,比如AWR里DB Time、Top SQL的Elapsed Time、Buffer Gets、Physical Reads这些值有没有明显下降。我习惯优化前后各拉一份AWR快照,对比同一个时间窗口内Top SQL的数据变化。
系统资源使用率,CPU使用率、I/O吞吐量、内存使用情况是否好转。这里要特别注意,有时候单条SQL优化的效果会被并发量的增长掩盖,所以至少观察一周的趋势,不要只看优化后几小时的数据。
稳定性回归,查看优化之后有没有引发新问题。比如新增索引后,相关的DML性能是否下降;修改了执行计划后,其他SQL是否因为统计信息或SPM策略受到影响。回归测试的覆盖面要包含正常路径和边界路径,比如数据量达到历史峰值时、并发达到峰值时,新方案是否还能扛住。
我实际工作中,优化完成后的验证期一般设定为三到五天,期间每天记录关键指标。如果三天内各项指标稳定或持续改善,才真正把工单关闭。如果出现反复,优先检查有没有其他任务干扰,或者是不是统计信息又被改变了。
再补充一个实践技巧:优化前先做好基线快照,包括AWR快照、执行计划快照、关键SQL的SQL_ID与执行统计,最好再截图留档。一旦优化后出现问题,就能快速回退或比对。没有基线的优化,基本等于闭着眼睛开车。
10. 写在最后:诊断能力是练出来的,不是背出来的
我见过很多同学花大量时间背等待事件清单、背AWR指标含义,但一遇到真实故障还是手足无措。原因是性能诊断不是背知识点,而是建立一套自己的排查闭环:从业务现象出发,定位到系统层,再收敛到数据库层,最后落到SQL和执行计划,每一个环节都要有数据和证据支撑,不能跳步,更不能靠猜。
FGMT12这期教程里讲的这些方法,听起来都不复杂,但真正难的是在压力下保持冷静,一步一步把问题缩小。我刚入行那两年,遇到生产事故总想一口吃成胖子,结果经常在错误的方向上折腾半天。后来养成了一个习惯:遇到性能问题,先在白板上把"现象、影响范围、最近变更、可能原因"四栏列出来,再决定从哪儿入手。变更往往是最大的嫌疑——发布新代码、调整参数、更新统计信息、改动表结构,这些都很容易成为性能突变的导火索。
如果你现在正在被一个数据库性能问题折磨,我建议先把这篇文章里的思路过一遍,特别是那条锁冲突的排查链路和AWR对比方法。冷静下来,按层次拆解,大部分问题都能找到清晰的答案。诊断Oracle性能,最怕的不是不懂技术,而是思路乱了。思路对了,工具和命令都是水到渠成的事。