Oracle执行计划 讲解(一)

前端之家收集整理的这篇文章主要介绍了Oracle执行计划 讲解(一)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

看懂Oracle执行计划是优化的第一步,让我们从下面的例子开始吧。

下面为补充内容

1、创建测试

  1. sql>createtabletasselect1id,object_namefromdba_objects;
  2. Tablecreated
  3. sql>updatetsetid=99whererownum=1;
  4. 1rowupdated
  5. commit;
  6. Commitcomplete
  7. indext_indont(id);
  8. Indexcreated

oracle优化器:RBO和CBO两种, 从oracle10g开始优化器已经抛弃了RBO,下面的列子说明CBO大概是怎样的

copy
    select/*+dynamic_sampling(t0)*/*fromtwhereid=1;
  1. 50819rowsselected.
  2. ExecutionPlan
  3. ----------------------------------------------------------
  4. Planhashvalue:1376202287
  5. -------------------------------------------------------------------------------------
  6. |Id|Operation|Name|Rows|Bytes|Cost(%cpu)|Time|
  7. |0|SELECTSTATEMENT||195|15405|51(0)|00:00:01|
  8. |1|TABLEACCESSBYINDEXROWID|T|195|15405|51(0)|00:00:01|
  9. |*2|INDEXRANGESCAN|T_IND|78||50(0)|00:00:01|
  10. PredicateInformation(identifiedbyoperationid):
  11. ---------------------------------------------------
  12. 2-access("ID"=1)

现象t表还没有被分析,提示/*+dynamic_sampling(t 0) */*的目的是让CBO无法通过动态采样获取表中的实际数据情况,此时CBO只能根据T表中非常有限的信息(比如表中的extents数量,数据块的数量)来猜测表中的数据。从结果中可以看到CBO猜出表中id=1的有195条,这个数值对于表的总数来说,是一个非常小的值,所以CBO选择了索引而不是全表扫描。
而实际情况如下所示:
copy
    select*whereid=1
  1. 2;
  2. 50819rowsselected.
  3. ExecutionPlan
  4. ----------------------------------------------------------
  5. Planhashvalue:1601196873
  6. --------------------------------------------------------------------------
  7. |Id|Operation|Time|
  8. |0|SELECTSTATEMENT||49454|3815K|67(2)|00:00:01|
  9. |*1|FULL|T|49454|3815K|67(2)|00:00:01|
  10. --------------------------------------------------------------------------
  11. 1-filter("ID"=1)

通过动态取样,CBO估算出行数为49454,非常接近于真实50820数目。选择了全表扫描。
我们来收集一下统计信息

copy
    execdbms_stats.gather_table_stats(user,'t',cascade=>true);
  1. Planhashvalue:1601196873
  2. SELECTSTATEMENT||50815|1339K|67(2)|00:00:01|
  3. |*1|FULL|T|50815|1339K|67(2)|00:00:01|
  4. PredicateInformation(identifiedbyoperationid):
  5. ---------------------------------------------------
  6. 1-filter("ID"=1)

现在扫描过的行数为50815。

如果我们更新了所有的id为99看看。

copy
    setid=99;
  1. 50820rowsupdated
  2. whereid=99;
  3. SELECTSTATEMENT||1|27|2(0)|00:00:01|
  4. INDEXROWID|T|1|27|2(0)|00:00:01|
  5. INDEXRANGESCAN|T_IND|1||1(0)|00:00:01|
  6. 2-access("ID"=99)

因为没有对表进行分析,所以表中的分析数据还是之前的信息,CBO并不知道。我们可以看出Rows值为1,也就是说CBO人为表T中的ID=99的值只有1条,所有选择仍然是索引。

我们收集一把统计信息。

copy
    PL/sqlproceduresuccessfullycompleted
  1. 1-filter("ID"=99)

上面为补充内容,下面正式开始


1、 sql的执行计划


创建测试表

copy
    tablet1(idint,153); background-color:inherit; font-weight:bold">namevarchar2(1000));
  1. tablet2(idindexind_t1ont1(id);
  2. Indexcreated
  3. indexind_t2ont2(id);
  4. indexind_t2_nameont2(name);
  5. insertintot1selecta.OBJECT_ID,a.OBJECT_NAMEfromall_objectsa;
  6. 50206rowsinserted
  7. intot2fromall_objectsawhererownum<=20;
  8. 20't1','t2',153); background-color:inherit; font-weight:bold">proceduresuccessfullycompleted


2、产生执行计划

copy
    fromt1,t2wheret1.id=t2.id;
  1. Planhashvalue:828990364
  2. --------------------------------------------------------------------------------------
  3. SELECTSTATEMENT||20|780|43(0)|00:00:01|
  4. INDEXROWID|T1|1|28|2(0)|00:00:01|
  5. |2|NESTEDLOOPS||20|780|43(0)|00:00:01|
  6. |3|FULL|T2|20|220|3(0)|00:00:01|
  7. |*4|INDEXRANGESCAN|IND_T1|1||1(0)|00:00:01|
  8. 4-access("T1"."ID"="T2"."ID")
  9. Statistics
  10. 1recursivecalls
  11. 0dbblockgets
  12. 37consistentgets
  13. 0physicalreads
  14. 0redosize
  15. 1452bytessentviasql*Nettoclient
  16. 503bytesreceivedviasql*Netfromclient
  17. 3sql*Netroundtripsto/fromclient
  18. 0sorts(memory)
  19. 0sorts(disk)
  20. 20rowsprocessed

看执行计划时,我们首先从缩进最大的行读取,它是最先被执行的步骤。在执行计划中:id=3和id=4是最先被执行的,
copy
    |3|FULL|T2|20|220|3(0)|00:00:01|
  1. |*4|INDEXRANGESCAN|IND_T1|1||1(0)|00:00:01|
两行缩进一样的,最上面的最先被执行,在这里就是id=3
copy
    FULL|T2|20|220|3(0)|00:00:01|
选择次之缩进的行数id=2,表连接方式为NESTED LOOPS。
copy
    |2|NESTEDLOOPS||20|780|43(0)|00:00:01|
然后是id=1,扫描表的方式为TABLE ACCESS BY INDEX ROWID
copy
    |1|INDEXROWID|T1|1|28|2(0)|00:00:01|
最后是id=0
copy
    |0|SELECTSTATEMENT||20|780|43(0)|00:00:01|
我们翻译成语言大概如下,
从t2表第一行读取,查看每一行是否符合下面条件:
"T1"."ID"="T2"."ID"
如果符合就拿出一行来,扫描整个t2表,这个过程就叫NESTED LOOPS
当整个t2表被扫描完之后,会产生一个结果集,这个结果集是IND_T1的一个索引集,然后oracle根据索引键值上的rowid去T1表中找到相应的记录,就是这一步:TABLE ACCESS BY INDEX ROWID
然后将结果返回:SELECT STATEMENT
id列为: id=3->id=4->id=2->id=1->id=0
让我们再看一看表中每一行表示什么含义:
1)Operation 列:当前操作的内容
2)Rows 列 :就是当前操作的 cardinality ,Oracle估算当前操作的返回结果集。
3)Cost (%cpu) : Oracle计算出来的一个数值(代价),用于说明sql执行的代价。
4)Time 列:Oracle估算当前操作的时间。

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("T1"."ID"="T2"."ID")

这里有access和filter区别,access就表示这个谓词的条件的值将会影响数据的访问路径(一般针对索引),filter只起过滤作用。
举个例子
copy
    fromt1wheret1.name='AA';
  1. norowsselected
  2. Planhashvalue:3617692013
  3. SELECTSTATEMENT||2|56|69(2)|00:00:01|
  4. FULL|T1|2|56|69(2)|00:00:01|
  5. 1-filter("T1"."NAME"='AA')

懂了吧。

下面我们来仔细分析Operation里面的内容

copy
    <prename="code"class="sql"><p></p><p><strong>a、表访问方式</strong></p><p><strong><spanstyle="font-family:宋体;color:#333333;font-size:14px;line-height:26px"><strong><spanstyle="font-family:宋体;font-size:9pt">1.FullTableScan(FTS)</span><spanstyle="font-family:宋体;font-size:9pt">全表扫描</span></strong></span>
  1. </strong></p><p>InaFTSoperation,thewholetableisreaduptothehighwatermark(HWM).TheHWMmarksthelastblockinthetablethathaseverhaddatawrittentoit.IfyouhavedeletedalltherowsthenyouwillstilltotheHWM.TruncateresetstheHWMbacktothestartofthetable.FTSusesmultiblocki/otoreadtheblocksfromdisk.<spanstyle="color:#ff0000;"></span><spanstyle="color:#ff0000;">--全表扫描模式下会读数据到表的高水位线(HWM即表示表曾经扩展的最后一个数据块),读取速度依赖于Oracle初始化参db_block_multiblock_read_count(我觉得应该这样翻译:FTS扫描会使表使用上升到高水位(HWM),HWM标识了表最后写入数据的块,如果你用DELETE删除了所有的数据表仍然处于高水位(HWM),只有用TRUNCATE才能使表回归,FTS使用多IO从磁盘读取数据块)</span>.</p><p>QueryPlan
  2. ------------------------------------
  3. SELECTSTATEMENT[CHOOSE]Cost=1
  4. **INDEXUNIQUESCANEMP_I1<spanstyle="color:#ff0000;">--如果索引里就找到了所要的数据,就不会再去访问表</span></p><p><spanstyle="color:#ff0000;">
  5. </span><strong>2.IndexLookup索引扫描</strong>
  6. Thereare5methodsofindexlookup:
  7. <strong>
  8. </strong></p><p><strong>1)indexuniquescan--索引唯一扫描</strong>
  9. Methodforlookingupasinglekeyvalueviaauniqueindex.alwaysreturnsasinglevalue,YoumustsupplyATLEASTtheleadingcolumntoaccessdataviatheindex.
  10. eg:sql>selectempno,enamefromempwhereempno=10;</p><p></p><prename="code"class="sql">sql>whereempno=10;
  11. Planhashvalue:2949544139
  12. SELECTSTATEMENT||1|20|1(0)|00:00:01|
  13. INDEXROWID|EMP|1|20|1(0)|00:00:01|
  14. UNIQUESCAN|PK_EMP|1||1(0)|00:00:01|
  15. 2-access("EMPNO"=10)
  16. 24recursivecalls
  17. 3consistentgets
  18. 385bytessentviasql*Net 481bytesreceivedviasql*Net 1sql*Netroundtrips 0rowsprocessed
  19. </pre><br>
  20. <strong>2)indexrangescan--索引局部扫描</strong><br>
  21. Indexrangescanisamethodforaccessingarangevaluesofaparticularcolumn.indexmustbesuppliedindex.Canbeusedforrangeoperations(e.g.><<>>=<=between).<br>
  22. <preselectempnowhereEMPNO>=7902;
  23. Planhashvalue:1567865628
  24. ---------------------------------------------------------------------------
  25. SELECTSTATEMENT||2|26|2(0)|00:00:01|
  26. INDEXRANGESCAN|PK_EMP|2|26|2(0)|00:00:01|
  27. ---------------------------------------------------------------------------
  28. 1-access("EMPNO">=7902)
  29. Note
  30. -----
  31. -dynamicsamplingusedforthisstatement
  32. 0recursivecalls
  33. 2consistentgets
  34. 569bytessentviasql*Net 492bytesreceivedviasql*Net 2sql*Netroundtrips 2 <br>
  35. <strong>3)fullscan--索引全局扫描</strong><br>
  36. indexscansareonlyavailableintheCBOasotherwiseweareunabletodeterminewhetherafullscanwouldbeagoodideaornot.WechooseanFullScanwhenwehavestatisticsthatindicatethatitisgoingtobemoreefficientthanatable
  37. scanandasort.Forexamplewemaydoaindexscanwhenwedoanunboundedscanofanindexandwantthedatatobeorderedorder.<br>
  38. orderbyempno;
  39. 14 Planhashvalue:179099197
  40. SELECTSTATEMENT||14|182|2(0)|00:00:01|
  41. |1|FULLSCAN|PK_EMP|14|182|2(0)|00:00:01|
  42. Note
  43. -----
  44. -forthisstatement
  45. Statistics
  46. 4recursivecalls
  47. 0dbblockgets
  48. 11consistentgets
  49. 0physicalreads
  50. 0redosize
  51. 676bytessentviasql*Nettoclient
  52. 492bytesreceivedviasql*Net 2sql*Netroundtrips 0sorts(memory)
  53. 0sorts(disk)
  54. 14rowsprocessed
  55. </pre><br>
  56. <br>
  57. <strong>4)indexfast--索引快速全局扫描,不带orderby情况下常发生</strong><br>
  58. Scansalltheblockindex,153); background-color:inherit; font-weight:bold">Rowsarenotreturnedinsortedorder,Introducedin7.3andrequiresV733_PLANS_ENABLED=TRUEandCBO,maybehintedusingINDEX_FFShint,usesmultiblocki/o,canbeexecutedinparallel,canbeusedtoaccesssecondcolumn
  59. ofconcatenatedindexes.Thisisbecauseweareselectingallindex.<br>
  60. <prefromemp;
  61. Planhashvalue:366039554
  62. -------------------------------------------------------------------------------
  63. SELECTSTATEMENT||14|182|2(0)|00:00:01|
  64. INDEXFASTFULLSCAN|PK_EMP|14|182|2(0)|00:00:01|
  65. -------------------------------------------------------------------------------
  66. 4recursivecalls
  67. 13consistentgets
  68. 676bytessentviasql*Net <strong>5)indexskipscan--索引跳跃扫描,where条件列是非索引的前导列情况下常发生</strong><br>
  69. Indexskipscanfindsrowseveniftheisnottheleadingofaconcatenatedindex.Itskipsthefirstcolumn(s)duringthesearch.<br>
  70. indexi_emponemp(empno,ename);
  71. Indexcreated.
  72. select/*+index_ss(empi_emp)*/jobwhereename='SMITH';
  73. Planhashvalue:98078853
  74. SELECTSTATEMENT||1|13|5(0)|00:00:01|
  75. INDEXROWID|EMP|1|13|5(0)|00:00:01|
  76. INDEXSKIPSCAN|I_EMP|1||4(0)|00:00:01|
  77. 2-access("ENAME"='SMITH')
  78. filter("ENAME"='SMITH')
  79. 5recursivecalls
  80. 513bytessentviasql*Net 1 <p></p>
  81. <p></p>
  82. <p><strong>3.Rowid物理ID扫描</strong><br>
  83. Thisisthequickestaccessmethodavailable.Oracleretrievesthespecifiedblockandextractstherowsitisinterestedin.<br>
  84. --Rowid扫描是最快的访问数据方式</p>
  85. whererowid='AAAjFUAAEAAABZ1AAM';
  86. Planhashvalue:1116584662
  87. -----------------------------------------------------------------------------------
  88. SELECTSTATEMENT||1|99|1(0)|00:00:01|
  89. BYUSERROWID|EMP|1|99|1(0)|00:00:01|
  90. -----------------------------------------------------------------------------------
  91. 1consistentgets
  92. 983bytessentviasql*Net 1 <p><br>
  93. </p>
  94. <p><strong>b、运算符</strong><br>
  95. 1.sort--排序,很消耗资源<br>
  96. Thereareanumberofdifferentoperationsthatpromotesorts:<br>
  97. (1)byclauses(2)groupby(3)sortmergejoin–-这三个会产生排序运算<br>
  98. 2.filter--过滤,如notin、min函数等容易产生<br>
  99. Hasanumberofdifferentmeanings,usedtoindicatepartitionelimination,mayalsoindicateanactualfilterstepwhereonerowsourceisfiltering,another,functionssuchminmayintroducefilterstepsintoqueryplans.<br>
  100. 3.view--视图,大都由内联视图产生(可能深入到视图基表)<br>
  101. Whenaviewcannotbemergedintothemainqueryyouwilloftenseeaprojectionviewoperation.Thisindicatesthatthe'view'willbeselectedfromdirectlyasopposedtobeingbrokendownintojoinsonthebasetables.Anumberofconstructsmakeaview
  102. nonmergeable.Inlineviewsarealsononmergeable.<br>
  103. </p>
  104. selectename,totfromemp,(sum(empno)totbyempno)tmpwhereemp.empno=tmp.empno;
  105. Planhashvalue:138960760
  106. -----------------------------------------------------------------------------------------
  107. SELECTSTATEMENT||14|644|4(25)|00:00:01|
  108. |1|MERGEJOIN||14|644|4(25)|00:00:01|
  109. |2|INDEXROWID|EMP|14|280|2(0)|00:00:01|
  110. |3|FULLSCAN|PK_EMPNO|14||1(0)|00:00:01|
  111. |*4|SORTJOIN||14|364|2(50)|00:00:01|
  112. |5|VIEW||14|364|1(0)|00:00:01|
  113. |6|HASHGROUPBY||14|182|1(0)|00:00:01|
  114. |7|FULLSCAN|PK_EMPNO|14|182|1(0)|00:00:01|
  115. -----------------------------------------------------------------------------------------
  116. 4-access("EMP"."EMPNO"="TMP"."EMPNO")
  117. filter("EMP"."EMPNO"="TMP"."EMPNO")
  118. 43recursivecalls
  119. 61consistentgets
  120. 821bytessentviasql*Net 5sorts(memory)
  121. 4.partition--分区视图<br>
  122. Partitionviewsarealegacytechnologythatweresupercededbythepartitioningoption.Thissectionofthearticleisprovidedasreferenceforsuchlegacysystems.<br>
  123. <p><strong>3、让我们再看看统计信息部分</strong></p>
  124. setautotracetraceonly;
  125. selectcount(*) Planhashvalue:2083865914
  126. -------------------------------------------------------------------
  127. Rows|Cost(%cpu)|SELECTSTATEMENT||1|3(0)|00:00:01|
  128. |1|SORTAGGREGATE||1|||
  129. |2|FULL|EMP|14|3(0)|00:00:01|
  130. 5recursivecalls(归调用次数)
  131. 0dbblockgets(从磁盘上读取的块数,即通过update/delete/selectforupdate读的次数)
  132. 15consistentgets(从内存里读取的块数,即通过不带updateselect读的次数)
  133. 0physicalreads(物理读—从磁盘读到数据块数量,一般来说是'consistentgets'+'dbblockgets')
  134. size(重做数——执行sql的过程中,产生的重做日志的大小)
  135. 515bytessentviasql*Net 0sorts(memory)(在内存中发生的排序)
  136. 0sorts(disk)(在硬盘中发生的排序)
  137. name="code"class="sql">说明:<prename="code"class="sql"><spanstyle="color:#CC0000;">Cost=(SingleblockI/Ocost+MultiblockI/Ocost+cpucost)/sreadtim</span></pre>
  138. <tableclass=""border="0"cellpadding="0"cellspacing="0"height="331"width="611">
  139. <colgroup><colwidth="90"><colwidth="257"><colwidth="461"></colgroup>
  140. <tbody>
  141. <trheight="38">
  142. <td>
  143. <p>序号</p>
  144. </td>
  145. <td>
  146. <p>列名</p>
  147. </td>
  148. <p>解释</p>
  149. </tr>
  150. <trheight="56">
  151. <p>1</p>
  152. <p>dbblockgets</p>
  153. <p>从buffercache中读取的block的数量</p>
  154. </tr>
  155. <p>2</p>
  156. <p>consistentgets</p>
  157. <p>从buffercache中读取的undo数据的block的数量</p>
  158. <trheight="18">
  159. <p>3</p>
  160. <p>physicalreads</p>
  161. <p>从磁盘读取的block的数量</p>
  162. <trheight="56">
  163. <p>4</p>
  164. <p>redosize</p>
  165. <p>DML生成的redo的大小</p>
  166. <p>5</p>
  167. <p>sorts(memory)</p>
  168. <p>在内存执行的排序量</p>
  169. <p>6</p>
  170. <p>sorts(disk)</p>
  171. <p>在磁盘上执行的排序量</p>
  172. </tbody>
  173. </table>
  174. <pre></pre>
  175. <pre></pre>
  1. </pre></pre></pre>

猜你在找的Oracle相关文章