ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oracle慢SQL优化:六种执行计划获取方法详解

Oracle慢SQL优化:六种执行计划获取方法详解 上周三晚上一条平时跑50毫秒的订单SQL突然涨到5秒我第一反应是Oracle执行计划又变了。结果EXPLAIN PLAN FOR跑出来计划走索引SET AUTOTRACE ON跑出来还是走索引consistent gets只有几百……可它为什么还是慢后来打开10046的trace文件才看到真相嵌套循环驱动表的返回行数暴增索引被反复扫描每次扫描都触发大量物理读。这个经历让我彻底想明白一件事在Oracle里“获取执行计划”从来不是一条路走到底。同一个SQL在不同时机、不同视角下能拿到的信息是完全不同的。很多人刚接触Oracle时只知道EXPLAIN PLAN等真正遇到性能问题就发现不够用了。这篇文章就把我日常工作里最常用的6种执行计划获取方法完整梳理一遍包括每种方法适合什么场景、输出怎么看、容易踩什么坑。无论你是刚入门的新人还是被慢SQL反复折磨的开发这6种方法都值得掌握。1. 先搞清楚执行计划在Oracle里到底“藏在哪”1.1 执行计划是优化器在不同时点留下的决策快照Oracle的CBO基于成本的优化器会结合统计信息、系统参数、绑定变量取值、数据分布等一堆输入估算每一种执行路径的成本然后挑选它认为最便宜的那条。问题是这些输入不是固定不变的——统计信息会更新绑定变量值会不同系统参数可能被调整数据分布更是一直在变。所以同一个SQL上午9点的执行计划和下午3点的执行计划可能是两个完全不同的计划。这意味着“执行计划”不是一个固定的东西更像是一个“决策快照”。你可以用EXPLAIN PLAN在没有真正执行SQL的情况下拿到优化器“纸上谈兵”的评估结果也可以用AUTOTRACE让SQL真实跑一遍拿到实际资源消耗还能从共享池里直接把正在使用的计划捞出来最夸张的是用10046事件把SQL执行过程中的每步耗时、每次等待事件全部记录到文件。再加上AWR历史快照连几天前的计划都能翻出来。理解了这个本质你就明白为什么Oracle不像MySQL那样一句EXPLAIN走天下而是提供了这么多入口。每一种入口其实都是在回答不同时间点、不同维度下的同一个问题这条SQL到底是怎么跑的。1.2 六种方法解决的是“查得到、查得准、查得全”三个层次我按自己的使用习惯把这6种方法整理成一个对照表方便你根据当前诉求快速选择方法是否真实执行SQL能否看到真实统计能否看到等待事件数据来源主要用途EXPLAIN PLAN否否否plan_table$快速评估、SQL改写验证AUTOTRACE是是逻辑读/物理读否当前会话 plustrace现场确认资源消耗DISPLAY_CURSOR否读已有执行记录可选ALLSTATS否共享池v$sql/v$sql_plan分析慢SQL的真实计划10046 tkprof是是是trace文件深挖等待事件与耗时V$SQL_PLAN否读已有记录可选否共享池动态视图批量分析、自动化巡检AWR DISPLAY_AWR否读历史快照部分快照统计否DBA_HIST_*计划丢失取证、历史对比前两种适合快速上手和理解第三种和第四种是SQL优化时的主力第五种适合写脚本做批量巡检第六种专门用来做回溯分析。后面我就按这个递进关系逐个展开。2. 方法一explain plan for——不真正执行SQL的“纸上推演”2.1 基本用法与配套的dbms_xplan.displayEXPLAIN PLAN是Oracle最传统的执行计划获取方式它不会真正执行SQL只是让优化器根据现有统计信息“推演”一遍执行路径把结果写进plan_table$这张表。基本用法很简单EXPLAIN PLAN FOR SELECT o.order_id, u.user_name FROM orders o, users u WHERE o.user_id u.user_id AND o.order_time DATE 2024-01-01;执行完这一步之后计划已经写入了plan_table$但要阅读它需要配合DBMS_XPLAN包SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());DBMS_XPLAN.DISPLAY会读取plan_table$里的内容格式化成人类可读的树形结构。你还可以给计划起个名字方便多个计划之间做对比EXPLAIN PLAN SET STATEMENT_IDplan_a FOR SELECT /* INDEX(o idx_order_time) */ order_id, user_id FROM orders o WHERE order_time DATE 2024-01-01; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(statement_id plan_a));这个方法最大的价值在于“零成本”。不管目标表有多大EXPLAIN PLAN都只是计算不会真正读取数据所以对生产环境几乎没有影响。配合提示hint快速验证索引是否被使用、验证改写后的SQL执行路径是否更优它都是第一选择。2.2 explain plan的输出和容易误导你的地方一次典型的输出长这样示意Plan hash value: 1234567890 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100 | 4500 | 68 (2)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID | ORDERS | 100 | 4500 | 68 (2)| 00:00:01 | |* 2 | INDEX RANGE SCAN | IDX_ORDER_TIME | 100 | | 3 (0) | 00:00:01 | ---------------------------------------------------------------------------------------------Predicate Information (identified by operation id):2 - access(ORDER_TIME TIMESTAMP 2024-01-01 00:00:00)注意看Rows这一列它表示优化器估算返回多少行不是实际返回多少行Cost也是估算成本不精确。对于初学者来说这可能是最容易误解的一点EXPLAIN PLAN里走索引不代表SQL真跑起来就一定走索引。 我踩过一个很典型的坑有一次统计信息刚更新完EXPLAIN PLAN FOR清晰地显示走索引范围扫描估算100行。但SQL真正跑的时候却变成了全表扫描。原因是有另一个会话正在执行大事务优化器结合实时系统信息做出了不同判断。所以拿EXPLAIN PLAN当最终结论是有风险的它只适合做快速验证不适合做故障最终定论。如果你发现某个SQL线上很慢、但EXPLAIN PLAN显示计划很漂亮那基本可以确定问题出在计划评估与实际执行环境产生了偏差需要用下面几种方法去拿到“真实计划”。 ## 3. 方法二set autotrace on——SQL*Plus下执行与统计一步到位 ### 3.1 权限配置和autotrace的几种变体 AUTOTRACE是SQL*Plus自带的工具它会让SQL真实执行一遍然后在终端里同时输出执行计划、统计信息以及默认的查询结果。这是我在测试环境最常用的方法因为可以同时看到“计划长什么样”和“实际消耗了多少资源”。 很多新手第一次用会报错提示没有权限。这是因为AUTOTRACE需要访问v$sesstat、v$statname、v$mystat等动态性能视图Oracle专门提供了一个PLUSTRACE角色。配置方法很简单 bash sqlplus / as sysdba ?/sqlplus/admin/plustrce.sql GRANT PLUSTRACE TO SCOTT;配置完成后再回到SQL*PlusSET AUTOTRACE ON; SELECT o.order_id, u.user_name FROM orders o, users u WHERE o.user_id u.user_id AND o.order_time DATE 2024-01-01;AUTOTRACE有几个变体日常使用要根据场景选择SET AUTOTRACE ON输出查询结果 执行计划 统计信息SET AUTOTRACE TRACEONLY不打印查询结果只输出计划和统计信息适合大结果集SET AUTOTRACE TRACEONLY STATISTICS只输出统计信息SET AUTOTRACE OFF关闭如果查询结果有几万行甚至更多千万别用ON模式终端会被刷爆。我一般在测试环境跑大SQL时直接先SET AUTOTRACE TRACEONLY这是很多人忽略的细节。3.2 统计信息该看哪几个数字AUTOTRACE的统计信息区域有十几个指标但真正需要关心的核心指标就这几个consistent gets逻辑读表示SQL执行过程中访问的内存块数。这是判断SQL好坏最稳定的指标比执行时间可靠得多。physical reads物理读表示从磁盘读取的块数。物理读高通常意味着I/O压力大或者数据没有全部缓存在内存中。sorts排序次数超过预期时要注意检查排序区参数和SQL写法。rows processed最终返回给客户端的行数。有一次我对比两条SQL的性能一条跑得快但consistent gets高达3万另一条慢一丢丢但consistent gets只有500。结论非常明显第一条SQL虽然这次“碰巧”快但逻辑读太高数据量一涨就会崩第二条是正确写法。这就是AUTOTRACE的价值它把SQL的真实“体力活”量摆在你面前而不是让你凭感觉猜。需要注意AUTOTRACE会真实执行SQL所以UPDATE、DELETE、MERGE这些DML语句在使用时务必小心不能在没把握的情况下对生产数据直接执行。还有如果是在生产环境排查重SQL我不建议用AUTOTRACE原地跑一遍因为重SQL本身就慢再跑一次会给数据库添负担。这种场景更适合用接下来要讲的方法直接从共享池拿信息。4. 方法三dbms_xplan.display_cursor——直接捞共享池里正在用的计划4.1 获取sql_id的常见姿势如果要评一个“DBA日常使用频率最高”的获取执行计划方法我会投给DBMS_XPLAN.DISPLAY_CURSOR。它不做任何SQL推演而是直接去共享池里把已经存在的执行计划读出来。这个计划是SQL真正执行时优化器生成的可能和EXPLAIN PLAN的结果不一样因为它包含真实运行时的多个决策因素。使用前提是先找到目标SQL的sql_id。我的习惯是从v$session入手找到正在跑的慢SQLSELECT sql_id, sql_text, child_number, executions, elapsed_time FROM v$sql WHERE sql_text LIKE %FROM orders o% ORDER BY last_active_time DESC;拿到sql_id之后再调用SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(5jy6a9x7qk1d2, 0, ALLSTATS LAST));第二个参数是child_number第三个参数是显示格式常用取值有TYPICAL、ALL、ALLSTATS LAST、ALL ALLSTATS LAST。如果只想看默认计划也可以简写成SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(5jy6a9x7qk1d2, NULL, ALL));4.2 gather_plan_statistics让输出带上真实行数DISPLAY_CURSOR默认只显示优化器的估算行数E-Rows如果想看到真实执行行数A-Rows、实际耗时A-Time、逻辑读Buffers必须让SQL在运行时记录执行统计信息。我通常是直接在SQL上加提示SELECT /* gather_plan_statistics */ o.order_id, u.user_name FROM orders o, users u WHERE o.user_id u.user_id AND o.order_time DATE 2024-01-01;SQL跑完后再用DISPLAY_CURSOR查看------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | ------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.25| 4567 | 890 | | 1 | NESTED LOOPS | | 1 | 100 | 10 |00:00:00.25| 4567 | 890 | | 2 | TABLE ACCESS BY INDEX ROWID| ORDERS | 1 | 100 | 10 |00:00:00.01| 40 | 2 | |* 3 | INDEX RANGE SCAN | IDX_ORDER_TIME | | 1 | 100 | 10 |00:00:00.01| 12 | 1 | | 4 | TABLE ACCESS BY INDEX ROWID| USERS | 10 | 1 | 10 |00:00:00.24| 4527 | 888 | |* 5 | INDEX UNIQUE SCAN | PK_USERS | 10 | 1 | 10 |00:00:00.01| 30 | 10 | ------------------------------------------------------------------------------------------------------------这段输出信息量非常大Starts该步骤被执行了多少次。注意第4行Starts10说明ORDERS表返回10行USERS表就被索引查询了10次。A-Rows实际返回行数。第2行A-Rows10但E-Rows100说明优化器高估了10倍。Buffers逻辑读次数。第4行Buffers4527说明嵌套循环里每次查USERS都消耗了巨大逻辑读。Reads物理读次数。有了真实行数和估算行数的对比慢SQL的病因就一目了然了。遇到E-Rows与A-Rows差额巨大的情况第一反应应该是检查统计信息是否过期、直方图是否缺失而不是急着改SQL。4.3 注意child cursor和权限DISPLAY_CURSOR有个很容易被忽略的细节一个sql_id可能对应多个child cursor。绑定变量值变化、优化器参数调整、DDL导致游标失效都会产生新的child。如果你只看了child_number0很可能看的是旧计划。我一般会先用这条SQL确认一下有几个childSELECT child_number, executions, buffer_gets, is_bind_sensitive, is_bind_aware FROM v$sql WHERE sql_id 5jy6a9x7qk1d2;然后挑选executions最大或最近活跃的那个child去展示计划。另外DISPLAY_CURSOR需要访问v$sql_plan普通用户通常看不到全局信息需要授权GRANT SELECT ON v_$sql TO scott; GRANT SELECT ON v_$sql_plan TO scott; GRANT SELECT ON v_$session TO scott;如果上面这些你都准备好了DISPLAY_CURSOR就能成为你排查慢SQL最锋利的一把刀。5. 方法四10046事件与tkprof——计划之外还要等谁5.1 开trace和定位trace文件有些SQL比较邪门执行计划看着正常逻辑读也不离谱但就是慢。这种时候问题往往不在执行计划本身而在等待事件上——SQL把时间花在了等待I/O、等待锁、等待网络传输这些地方。要看到等待事件就得靠10046事件。10046事件是Oracle提供的一种诊断事件它会把SQL执行过程中的详细活动记录到操作系统trace文件中。按level不同记录的信息详细程度不同level 1执行计划level 4增加绑定变量信息level 8增加等待事件信息level 12绑定变量 等待事件全部记录最常用的是level 12。开trace的基本操作ALTER SESSION SET EVENTS 10046 trace name context forever, level 12; -- 在这里执行你想分析的SQL ALTER SESSION SET EVENTS 10046 trace name context off;执行完SQL后trace文件会生成在数据库的诊断目录下。11g之后定位路径的规范做法是SELECT value FROM v$diag_info WHERE name Diag Trace;文件命名一般是实例名_ora_进程号.trc比如orcl_ora_12345.trc。5.2 tkprof格式化的关键字段与等待事件读取trace文件是纯文本直接看非常痛苦需要用tkprof工具格式化tkprof /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_12345.trc \ /tmp/tkprof_out.txt \ sysno格式化后的文件里最值得关注的是每一段SQL对应的Row Source Operation部分Rows Row Source Operation ------- --------------------------------------------------- 10422 NESTED LOOPS 10422 TABLE ACCESS BY INDEX ROWID ORDERS 10422 INDEX RANGE SCAN IDX_ORDER_TIME 15670 TABLE ACCESS BY INDEX ROWID USERS 15670 INDEX UNIQUE SCAN PK_USERS这里的Rows是实际执行的累计行数能直接看出每层操作到底处理了多少行。如果计划显示NESTED LOOPS但驱动表实际返回了10422行而非估算的100行那慢的原因就清楚了一大半。更关键的是文件后半部分的等待事件汇总Event waited on Times Max. Wait Total Waited ---------------------------------------- Waited ---------- ------------ db file sequential read 142 0.03 1.87 SQL*Net message to client 5 0.00 0.00db file sequential read大量出现说明索引读请求分散在磁盘上每次都在等待物理I/O。enq: TX - row lock contention大量出现说明在等行级锁。SQL*Net message from client时间占比高说明可能应用端处理结果太慢数据库本身没毛病。这些信息只有10046能给到。有一次生产环境一个SQL诡异慢计划正常、逻辑读不高但每次固定卡4秒。用10046一看142次db file sequential read总等待1.87秒单次平均0.03秒左右。后续定位到是索引碎片化加上数据分布改变导致单次读都很慢重建索引后问题消失。如果你只盯着执行计划可能永远找不到这个原因。开10046时要注意几点一是必须及时关闭否则trace文件会一直增长磁盘被写满的案例我见过不止一次二是不要对整个库开10046这会瞬间产生海量文件三是生产环境尽量用DBMS_MONITOR.SESSION_TRACE_ENABLE按会话开启更精细。6. 方法五v$sql_plan——把执行计划当成普通数据表来查6.1 v$sql_plan核心字段共享池里所有的执行计划底层都存在动态性能视图v$sql_plan中。这张视图的每一行对应执行计划中的一个步骤。很多人只知道用DBMS_XPLAN包去“展示”计划却没想到可以直接把它当成一张数据表去“查询”。核心字段如下ID步骤编号从0开始PARENT_ID父步骤编号用来重建层级关系OPERATION和OPTIONS操作类型比如TABLE ACCESS、INDEX RANGE SCAN、SORT ORDER BYOBJECT_NAME涉及的索引名或表名CARDINALITY优化器估算的行数BYTES估算的字节数COST、CPU_COST、IO_COST成本估算一条最简单的查询SELECT sql_id, child_number, id, parent_id, operation, options, object_name, cardinality, cost FROM v$sql_plan WHERE sql_id 5jy6a9x7qk1d2 ORDER BY child_number, id;6.2 用connect by重建成树形展示直接查出来的是平面记录看起来不直观。好在Oracle有CONNECT BY可以按PARENT_ID递归地把计划重建成树形缩进格式SELECT LPAD( , 2 * (LEVEL - 1)) || operation || || options || || object_name AS plan_line FROM v$sql_plan START WITH sql_id 5jy6a9x7qk1d2 AND child_number 0 AND id 0 CONNECT BY PRIOR id parent_id AND sql_id 5jy6a9x7qk1d2 AND child_number 0;这条SQL输出的结果和EXPLAIN PLAN的树形展示非常接近但它来自共享池真实计划可以做进一步加工。配合v$sql还能直接找出“所有使用了某个大索引的SQL”或者“所有执行次数特别多的SQL”这就让执行计划进入了数据化分析阶段。6.3 批量巡检脚本的思路v$sql_plan真正的威力体现在批量巡检。我维护的数据库有上百个应用连接想人工逐个看SQL是不可能的。我的做法是写一个定时任务定期把逻辑读排名靠前的SQL的执行计划落表再通过对比plan_hash_value来判断计划是否发生变化。大概逻辑是这样INSERT INTO sql_plan_snapshot SELECT p.sql_id, p.child_number, p.id, p.parent_id, p.operation, p.options, p.object_name, s.plan_hash_value, SYSDATE FROM v$sql_plan p, v$sql s WHERE p.sql_id s.sql_id AND p.id 0 AND s.buffer_gets 100000;第二天早上如果某个SQL的plan_hash_value变了就能精准定位到“它的执行计划在某个时间点发生了切换”。配合AWR还能反查切换前后的性能数据这种监控方式比人肉排查高效得多。需要注意v$sql_plan只保存共享池中仍然存在的SQL。SQL一旦被age out这里就查不到了。所以需要更长历史的计划就要用下一种方法AWR。7. 方法六AWR里的历史执行计划——给了SQL一栋“档案楼”7.1 从dba_hist_sqltext反查sql_idAWR自动快照会定期收集数据库的统计信息其中就包括SQL的执行计划和执行统计。当SQL已经不在共享池或者你想知道“上周三那天的执行计划是什么”AWR是唯一选择。有个批处理任务每天晚上跑今天突然慢了。SQL还在不在共享池里不好说但AWR快照大概率有记录。第一步是反查sql_id我用的是dba_hist_sqltextSELECT sql_id, sql_text FROM dba_hist_sqltext WHERE sql_text LIKE %FROM orders o%;注意dba_hist_sqltext中的SQL文本可能被截断模糊查询时尽量用句子中比较有辨识度的片段而不是整个SQL。拿到sql_id后再看看它的历史趋势SELECT sql_id, plan_hash_value, TO_CHAR(begin_interval_time, YYYY-MM-DD HH24:MI) begin_time, executions_delta, elapsed_time_delta FROM dba_hist_sqlstat WHERE sql_id 5jy6a9x7qk1d2 ORDER BY begin_interval_time;如果执行计划真的变了这条查询会直接显示plan_hash_value在不同快照间的跳变。plan_hash_value是执行计划结构的“指纹”只要操作顺序、操作类型、访问对象一致值就相同一旦哪个环节变了值就会变。用它来快速判断计划是否切换非常高效。7.2 dbms_xplan.display_awr与plan_hash_value对比想看某个历史时点的计划细节用DBMS_XPLAN.DISPLAY_AWRSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR(5jy6a9x7qk1d2, NULL, ALL));第二个参数填plan_hash_value可以只展示特定版本的计划SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR(5jy6a9x7qk1d2, 2774998642, ALL));这样就能对比历史计划和当前计划的具体差异是索引选择变了、连接顺序变了还是访问路径从NESTED LOOPS变成了HASH JOIN。AWR的默认保留期是8天超过这个时间的历史快照会被清理。如果你有长期关注的业务SQL建议提前创建基线EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_BASELINE( start_snap_id 100, end_snap_id 110, baseline_name bsln_order_daily);创建基线之后相关快照就不会被自动清理SQL计划也就有了长期档案。我遇到过不少开发同事问“为什么AWR里查不到那条SQL”多半是快照保留期已过或者SQL太短、太简单没有被快照采样到。碰到后面这种情况可以去v$active_session_history里找ASH记录作为补充。8. 六种方法同场对照一条慢SQL的排查路径8.1 同一个SQL六种方式分别看到什么想象一个场景orders表和users表关联查询一条SQL的耗时从200ms涨到了8秒。六种方法在同一个SQL上给你的信息完全不同方法你看到的典型内容能回答的问题EXPLAIN PLAN计划走索引估算行数100优化器“认为”它应该怎么跑AUTOTRACE实际常见consistent gets从几百涨到10万它实际消耗了多少逻辑读DISPLAY_CURSORA-Rows10万E-Rows100Starts巨大优化器估算偏差出在哪一步10046 tkprof等待事件集中在db file sequential read瓶颈是磁盘I/O还是CPU还是锁V$SQL_PLAN计划作为数据行可批量统计全库还有哪些SQL在用同一个慢索引AWR昨天计划hash是A今天是B计划从哪个时间点开始变的实际排查中这六种方式完全可以串成一条链。发现问题后用DISPLAY_CURSOR确认当前真实计划用ALLSTATS LAST看实际行数和估算行数的差异如果计划正常但就是慢开10046看等待事件要判断问题是何时出现的回AWR对比plan_hash_value的变化平时再用v$sql_plan写脚本做常态化巡检。EXPLAIN PLAN和AUTOTRACE更多承担快速验证的角色。8.2 我日常的排查顺序与选择逻辑我的个人习惯是这样的供参考先查v$session找到正在跑的慢SQL的sql_id用DBMS_XPLAN.DISPLAY_CURSOR看当前计划带ALLSTATS LAST如果SQL还没跑完先等它结束或杀掉会话对比E-Rows和A-Rows差距大就先更新统计信息或补直方图计划看着没毛病但SQL还是慢就在测试环境复现并开10046看等待事件SQL已经不在库里了直接去AWR取历史计划平时写些基于v$sql_plan的巡检脚本把异常计划变化提前发现。经常有人问我为什么不用AUTOTRACE开头。因为AUTOTRACE是真的会把SQL执行一遍在正在出问题的生产环境为了看一条慢SQL再去原样跑一次既增加负担又有风险。它更适合在测试环境做验证而不是生产故障排查的第一步。这也侧面说明每种方法没有绝对的好坏只有场景是否匹配。9. 踩过的坑和最终建议9.1 五个容易误判执行计划的操作教训坑点原因正确做法EXPLAIN PLAN显示走索引实际跑全表扫描不执行SQL估算信息与真实运行环境脱节用DISPLAY_CURSOR或AUTOTRACE验证AUTOTRACE打印几百万行把终端卡死ON模式会返回完整结果集先执行SET AUTOTRACE TRACEONLYDISPLAY_CURSOR拿到的是旧child一个sql_id可能对应多个child cursor按last_active_time取最新child10046一直不关闭磁盘被写满level 12记录等待事件文件增长极快用DBMS_MONITOR按会话控制及时关闭AWR查不到SQL快照没抓到短SQL或超过保留期提前创建基线结合ASH补充9.2 给初学者的工具使用顺序建议如果你刚开始熟悉Oracle建议按这个顺序去练会顺畅很多入门先用EXPLAIN PLAN DBMS_XPLAN.DISPLAY把执行计划的基本格式看懂日常验证在测试环境用AUTOTRACE理解consistent gets、physical reads这些指标深入优化重点掌握DISPLAY_CURSOR ALLSTATS LAST这能覆盖80%的慢SQL场景复杂问题学10046 tkprof搞清楚等待事件自动化运维尝试用v$sql_plan写批量巡检脚本回溯分析最后再掌握AWR历史计划遇到“昨天还正常今天突然慢”的问题就能派上用场。我自己早期总迷信执行计划本身觉得拿到计划就能找到原因后来才发现执行计划的真正价值在于“对比”计划与真实行数的对比、当前计划与历史计划的对比、逻辑读与等待事件的对比。Oracle提供这么多获取执行计划的入口本质上都是在帮你从不同维度找到那个微小的差异点。希望这篇文章能让你在下次被慢SQL折腾时少走一些我走过的弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进