<form id="tznrh"><form id="tznrh"><th id="tznrh"></th></form></form>

            在第8小節中,我們舉了一個很簡單的hash join的例子,但是它的每個操作僅僅執行一次,很難讓我們對Oracle的預測有一個全面的認識。我們需要一些比較復雜的執行計劃,比如父操作會調用許多次子操作,這也是我們在第9與第10部分所做的實驗。


            Getting started


            由于基于成本查詢變化的不斷進化,很難找到一個簡單的執行計劃示例來真正顯示它“確實應該”顯示的信息,所以我會展示一些數字顯示不正確的執行計劃,并且解釋這些反常的現象。這個小節我們來看一個簡單的嵌套循環連接:


            select  t1.id, t2.id
             from  t2, t1
             where  t2.n1 = 15  and t1.n1 = t2.n2
            ; ---------------------------------------------------------------------------------------  | Id  | Operation                     | Name  | Rows  | Bytes | Cost (%CPU)| Time     | ---------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT              |       |   225 |  4500 |    46   (0)| 00:00:01 |
             |   1 |  NESTED LOOPS                 |       |   225 |  4500 |    46   (0)| 00:00:01 |
             |   2 |   NESTED LOOPS                |       |   225 |  4500 |    46   (0)| 00:00:01 |
             |   3 |    TABLE ACCESS BY INDEX ROWID| T2    |    15 |   180 |    16   (0)| 00:00:01 |
             |*  4 |     INDEX RANGE SCAN          | T2_I1 |    15 |        |     1   (0)| 00:00:01 |
             |*  5 |    INDEX RANGE SCAN           | T1_I1 |    15 |       |     1   (0)| 00:00:01 |
             |   6 |   TABLE ACCESS BY INDEX ROWID | T1    |    15 |   120 |     2   (0)| 00:00:01 | ---------------------------------------------------------------------------------------  Predicate Information (identified by operation id): ---------------------------------------------------    4 - access("T2"."N1"=15)
               5 - access("T1"."N1"="T2"."N2")

            這個執行計劃展示了一個雙重嵌套的操作。操作的順序是4,3,5,2,6,1,0:對索引T2_I1做索引范圍掃描確定T2中的行,對獲取的T2中的每一行都對T1_I1做索引范圍掃描,之后我們根據T1_I1中獲取到的每個rowid去訪問T1的行。執行計劃預測的行總數本質上是對的—-我們的數據集從T2里提供15行,每個行會連接T1中的15行,所以總數是225.但是這些數據實際告訴了我們什么?

            接下來我會忽略Bytes列,我們已經知道它是怎么來的,而且它的準確性很差。同樣也會忽略Time列,因為我們知道他是從Cost列衍生出來的,通過Cost乘以sys.aux_stats$中的sreadtim得到的。

            第4行和第3行告訴我們我們獲取了15個rowid,之后獲取15行,兩個操作的成本分別為1和16—一個葉塊的物理讀,之后是15個表塊的訪問加上一個葉塊讀所以總成本是16。

            第5行我們對T1_I1做索引范圍掃描,找到15行的成本為1.這些數據是一次的預測,但是當查詢執行時,優化器不希望第5行僅僅執行一次。


            Hitting Problems


            優化器期望總共做15次索引范圍掃描,這也是為什么第2行中嵌套循環告訴我們會生成225行的原因,但是成本46是從哪里來的?答案也很簡單:這是個bug。第2行的成本應該是31,計算方法為16(執行一次第3行)+1(執行一次第5行)*15(由于第3行生成15行,需要執行15次)。

            暫時不看為什么是46而不是31這個問題,我們先看第6行,它展示的數字完全是錯誤的。對于第2行中生成的225行,每一行都需要執行一次第6行找到表T1中的行(并不是預測的15行),而且通過rowid單次執行表訪問的適合的成本是1(而不是預期的2)。

            這里的數據之所以不正確是有原因的:它們是為了反映優化器在Oracle 8.1及更早版本中使用的執行策略而設計的,而優化器在Oracle 8.1及更早版本中使用的執行策略應該是這樣的:

             
            
            
            --------------------------------------------------------------------------------
            
             | Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop | --------------------------------------------------------------------------------
            
             | SELECT STATEMENT          |          |   225 |    4K|     35 |       |       |  |  NESTED LOOPS             |          |   225 |    4K|     35 |       |       |  |   TABLE ACCESS BY INDEX RO|T2        |    15 |  180 |      5 |       |       |  |    INDEX RANGE SCAN       |T2_I1     |    15 |      |      1 |       |       |  |   TABLE ACCESS BY INDEX RO|T1        |     3K|   23K|      2 |       |       |  |    INDEX RANGE SCAN       |T1_I1     |     3K|      |      1 |       |       | --------------------------------------------------------------------------------
            

            在這個例子中,訪問T1和它的索引的Rows值是完全錯誤的,但是這個執行計劃也告訴了我們,”T2中返回的每行,我們都會通過索引范圍掃描來獲取T1中的每行(15,而不是3000)”,索引范圍掃描的成本是1(1個葉塊),訪問表的成本是2(需要加上子操作索引掃描),所以總的嵌套循環的成本是:5(訪問表2)+2(訪問表1)*15(預測訪問表1次數)=35。

            因此,我們最初計劃的第6行是報告8i中相應的操作的行數的,即使執行計劃和操作原理不一樣了。技術上來說,我認為第6行應該是這樣的:

             
            
            
            ---------------------------------------------------------------------------------------
            
             | Id  | Operation                     | Name  | Rows  | Bytes | Cost (%CPU)| Time     | ---------------------------------------------------------------------------------------
            
             |   6 |   TABLE ACCESS BY INDEX ROWID | T1    |     1 |     8 |     1   (0)| 00:00:01 | ---------------------------------------------------------------------------------------
            

            每次我們執行,都會訪問一個數據塊并且獲取一行數據。但是,這行會被執行225次,意味著會產生大量的成本。

            我們陷入了一個陷阱,一方面優化器使用傳統的算法來確定資源需求,另一方面執行計劃顯示了執行引擎實際采取的策略??梢哉f,我們可以在Oracle展示的方案和下面的方案之間進行選擇,從某些方面來說,下面的方案更真實一些。

             
            
            
            ---------------------------------------------------------------------------------------
            
             | Id  | Operation                     | Name  | Rows  | Bytes | Cost (%CPU)| Time     | ---------------------------------------------------------------------------------------
            
             |   0 | SELECT STATEMENT              |       |   225 |  4500 |    46   (0)| 00:00:01 |  |   1 |  NESTED LOOPS                 |       |   225 |  4500 |    46   (0)| 00:00:01 |  |   2 |   NESTED LOOPS                |       |   225 |  2700 |    31   (0)| 00:00:01 |  |   3 |    TABLE ACCESS BY INDEX ROWID| T2    |    15 |   180 |    16   (0)| 00:00:01 |  |*  4 |     INDEX RANGE SCAN          | T2_I1 |    15 |       |     1   (0)| 00:00:01 |  |*  5 |    INDEX RANGE SCAN           | T1_I1 |    15 |       |     1   (0)| 00:00:01 |  |   6 |   TABLE ACCESS BY INDEX ROWID | T1    |     1 |     8 |     1   (0)| 00:00:01 | ---------------------------------------------------------------------------------------
            

            這個版本的執行計劃,我調整了第2行的cost和bytes,第6行的cost和bytes。沒有調整的是第1行的cost,即使我們可以通過計算得出cost應該是256(31+225*1)。問題是第6行中成本為1是不確定的。因為T1中的數據有可能很好的被聚集了,我們不需要每次執行時都去讀取數據塊,我們可能只需要在第一次讀取數據塊,之后的14次可以從內存里讀取,所以平均的成本應該是1/15.(注意:事實上,優化器的確會將算術運算精確到小數點后幾位,但會將執行計劃中的報告數字四舍五入;這就是為什么有時你會看到一些數字,比如2 * 3 = 5,但在內部它可能是1.8 * 2.6 = 4.68)。

            就像這個例子看到的,存在一些與執行計劃相關的問題。當你試圖創建一些簡單的規則來理解這些數字時,你會發現一些異常。有時候基本策略“子操作累加至父操作”說不通時,嘗試一些靈活的變化,可以讓你更好的重新解釋這些數字。




            沃趣科技,讓客戶用上更好的數據庫技術!
            三分快3