程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> Oracle數據庫 >> Oracle數據庫基礎 >> 數據庫性能調優技術之嵌套循環執行計劃

數據庫性能調優技術之嵌套循環執行計劃

編輯:Oracle數據庫基礎
一、概述

  單表執行計劃是理解多表執行計劃的基礎。

  兩張表的連接有三種執行方式:1)嵌套循環連接;2)散列連接;3)歸並連接。兩張表連接時選擇這三種中的哪一種呢?這取決於索引、以及連接的代價。在該系列的第三篇(本文)文章中講解嵌套循環連接,第四篇文章中講解散列連接,第五篇文章中講解歸並連接。在第六篇以後會分析IN子查詢以及EXISTS子查詢。

  達夢數據庫、Oracle數據庫、SQL Server數據庫在數據庫執行計劃方面並無本質區別,因此上篇文章使用達夢數據庫作為實例數據庫進行分析,這篇文章我們選擇Oracle 10g作為實例數據庫。

  讀完本文後,應該能夠讀懂這三個數據庫的嵌套循環連接執行計劃。

  另外需要申明一點的是:因為Oracle的源代碼是不公開的,我這裡描寫的是根據執行計劃、成本代價以及10053文件進行反推的結果,盡管這樣,從大的方向上講,不會出現問題,僅做拋磚引玉。

  二、深入理解嵌套循環執行計劃

  Oracle數據庫常用的顯示執行計劃的方式有兩種:

  1) set autotrace on 命令;

  2) explain plan for 命令;

  舉例說明使用set autotrace命令:

  SQL> create table t1(c1 int,c2 int);
  Table created.
  SQL> create index it1c1 on t1(c1);
  Index created.
  SQL> insert into t1 values(1,1);
  1 row created.
  SQL> insert into t1 values(2,2);
  1 row created.
  SQL> commit;
  Commit complete.
  SQL> set autotrace on explain;
  SQL> select c1 from t1 where c1=1;

  我們可以看到,執行了“set autotrace on explain;”語句之後,接下來的查詢、插入、更新、刪除語句就會顯示執行計劃,直到執行“set autotrace off;”語句。如果是設置了“set autotrace on;”,除了會顯示執行計劃之外,還會顯示一些有用的統計信息。本系列文章不涉及查詢代價的評估分析。

  我們從上一段代碼中,我們發現在顯示“select c1 from t1 where c1=1;”執行計劃之前顯示了該執行語句的查詢結果。這說明:顯示執行計劃之前就真正地將該查詢語句執行了一遍。這樣會帶來一個不好後果,假設我們現在有一條語句,執行的時間需要半個小時,即使我們僅僅需要知道該語句的執行計劃,此種情況下,我們必須等待半個小時。因此,如果查詢的性能很慢,我們可以選擇選擇使用explain plan for命令。


  舉例說明explain plan for命令:

SQL> explain plan for select c1 from t1 where c1=1;
Explained.
SQL> select * from table(DBMS_XPLAN.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2624316456
--------------------------------------------------------------------------
| Id  | Operation        | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT |       |     1 |    13 |     1   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| IT1C1 |     1 |    13 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identifIEd by Operation id):
---------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   1 - Access("C1"=1)
Note
-----
   - dynamic sampling used for this statement
17 rows selected.
SQL>

  用“explain plan for 查詢語句;”生成執行計劃,然後使用“select * from table(DBMS_XPLAN.display);”語句顯示執行計劃。

  下面的內容,將通過一些例子來理解嵌套理解執行計劃:

  1.不帶索引的嵌套連接的執行計劃該如何理解?

  構造處測試場景:

  查詢語句為:

select /*+ USE_NL(t2) */ c1,c2 from t1 inner join t2 on c1=d2;

  該語句中“/*+ USE_NL(t2) */”是我們常說的hint提示,這裡的USE_NL告訴優化程序使用嵌套連接對表進行連接,t2為內部表。此查詢語句的執行計劃為:

0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=2 Bytes=78)
   1    0   NESTED LOOPS (Cost=4 Card=2 Bytes=78)
   2    1     TABLE Access (FULL) OF 'T1' (TABLE) (Cost=2 Card=2 Bytes
          =52)
   3    1     TABLE Access (FULL) OF 'T2' (TABLE) (Cost=1 Card=1 Bytes
          =13)

  “Execution Plan”顯示優化程序用來執行查詢的步驟。每一步都被賦予一個ID值(以0開始)。第二個數字顯示當前操作符的父結點。在這個執行計劃中,“NESTED LOOPS”的父結點是“SELECT STATEMENT”,“TABLE ACCESS (FULL) OF 'T1' (TABLE)”與“TABLE ACCESS (FULL) OF 'T2' (TABLE)”的父結點都是“NESTED LOOPS”。也可能稱為,操作符“SELECT STATEMENT”的孩子結點是“NESTED LOOPS”,操作符“NESTED LOOPS”的第一個孩子結點是“TABLE ACCESS (FULL) OF 'T1' (TABLE)”,操作符“NESTED LOOPS”的第二個孩子結點是“TABLE Access (FULL) OF 'T2' (TABLE)”。

  第二行表示,對表T1進行全表掃描,括號中的三個值是該步驟的成本代價,這裡不作闡述。第三行表示,對T2進行全表掃描,這裡還隱藏了一個細節:此處進行了c1=d1的判斷。參考explain plan for生成的執行計劃:

SQL>  explain plan for select /*+ USE_NL(t2) */ c1,c2 from t1 inner join t2 on c
1=d2;
Explained.
SQL> select * from table(DBMS_XPLAN.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 4033694122
---------------------------------------------------------------------------
| Id  | Operation              | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     2 |    78 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS      |      |     2 |    78 |     4   (0)| 00:00:01 |
|   2 |   TABLE Access FULL| T1   |     2 |    52 |     2   (0)| 00:00:01 |
|*  3 |   TABLE Access FULL| T2   |     1 |    13 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identifIEd by Operation id):
---------------------------------------------------
   3 - filter("C1"="D2")
Note
-----
   - dynamic sampling used for this statement
19 rows selected.
SQL>

  這裡顯示的步驟0、1、2、3與前面通過set autotrace on命令顯示的執行計劃在意義上是一樣的。紅顏色表明t2只能掃描到符合過濾條件c1=d1的記錄才會將控制權傳給父節點“NESTED LOOPS”。

  對於該查詢語句的執行,如果用代碼可以描述成這樣:

for (rec1 is t1’s first record; rec1!=NULL; rec1=rec1->next)
   for(rec2 is t2’s first record; rec2!=NULL; rec2=rec->next)
   {
      if(rec1.c1==rec2.d1)
           put  result(rec1.c1,rec1.c2) into result set;
   }

  也就是說,t1與t2先生成笛卡爾集,然後過過濾條件c1=d1過濾該笛卡爾集。

  其實,數據庫執行該語句的步驟也是類似的,下面是執行該語句的步驟:

1) TAF(T1)(“TABLE Access (FULL) OF 'T1'”的簡寫)取得T1的第一條記錄(1,1)傳遞給NL(“NESTED LOOPS”的簡寫),將控制權傳遞給操作符NL。
2) 操作符NL將控制權傳給第二個孩子TAF(T2)(“TABLE Access (FULL) OF 'T2'”的簡寫)。
3) TAF(T2)取得T2的第一條記錄(1,1),符合過濾條件c1=d1,將控制權傳給操作符NL。
4) NL將記錄(1,1)傳給SS(“SELECT STATEMENT”的簡寫),將控制權傳給SS。
5) SS將記錄(1,1)放入結果集合,將控制權限傳給NL。
6) NL將控制權限傳給TAF(T2)。
7) TAF(T2)取得T2表的下一條記錄(2,2),不符合條件c1=d1;取得下一條記錄(3,3),不符合條件(4,4)。取得下一條記錄,取不到記錄。T2表掃描結束。將控制權限傳遞給NL。
8) NL將控制權限傳給第一個孩子TAF(T1)。
9) TAF(T1)取得T1表的下一條記錄(2,2)傳遞給NL,將控制權傳給NL。
10) NL將控制權傳給第二個孩子TAF(T2)。
11) TAF(T2)取得T2的第一條(1,1),不符合過濾條件c1=d1;取得下一條記錄(2,2),滿足條件c1=d1,將控制權傳給操作符NL。
12) NL將記錄(2,2)傳給SS,將控制權傳給SS。
13) SS將記錄(2,2)放入結果集,將控制權傳給NL。
14) NL將控制權限傳給TAF(T2)。
15) TAF(T2)取得T2的下一條記錄(3,3),不符合過濾條件c1=d1;取得下一條記錄(4,4),不符合過濾條件c1=d1;取得下一條記錄,取不到記錄。T2表掃描結束。將控制權限傳遞給NL。
16) NL將控制權限傳給第一個孩子TAF(T1)。
17) TAF(T1)取得T1表的下一條記錄,取不到記錄,T1表掃描結束。將控制權傳給NL,通知NL掃描結束。
18) NL將控制權限傳給SS,通知SS操作結束。
19) SS將結果集(包含記錄(1,1)、(2,2))發送給客戶端。

  在上面的例子中,只查詢顯示t1的列,如果要顯示t2的列,情況是一樣,只是TAF(T2)需要將符合條件的T2記錄傳遞給NL,然後NL組合成符合條件的(c1,c2,d1,d2)傳遞給SS。

select /*+ USE_NL(t2) */ c1,c2,d1,d2 from t1 inner join t2 on c1=d2;

  對應的執行計劃:

0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=2 Bytes=104
          )
   1    0   NESTED LOOPS (Cost=4 Card=2 Bytes=104)
   2    1     TABLE Access (FULL) OF 'T1' (TABLE) (Cost=2 Card=2 Bytes
          =52)
   3    1     TABLE Access (FULL) OF 'T2' (TABLE) (Cost=1 Card=1 Bytes
          =26)

  2.使用非唯一索引的嵌套連接的執行計劃該如何理解?

  測試數據與1中描述的一樣。

  查詢語句:

select /*+ index(t2) */ c1,c2,d1 from t1 inner join t2 on c1=d1;

  對應的執行計劃:

   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=2 Bytes=78)
   1    0   NESTED LOOPS (Cost=4 Card=2 Bytes=78)
   2    1     TABLE Access (FULL) OF 'T1' (TABLE) (Cost=2 Card=2 Bytes
          =52)
   3    1     INDEX (RANGE SCAN) OF 'IT2D1' (INDEX) (Cost=1 Card=1 Byt
          es=13)

  使用explain plan對應的執行計劃:

SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2841753667
----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     2 |    78 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS      |       |     2 |    78 |     4   (0)| 00:00:01 |
|   2 |   TABLE Access FULL| T1    |     2 |    52 |     2   (0)| 00:00:01 |
|*  3 |   INDEX RANGE SCAN | IT2D1 |     1 |    13 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identifIEd by Operation id):
---------------------------------------------------
   3 - Access("C1"="D1")    //此處的Access表示使用值3去命中索引IT2D1對應的B樹。
Note
-----
   - dynamic sampling used for this statement
19 rows selected.
SQL>
   對於該查詢語句的執行,如果用代碼可以描述成這樣:
for (rec1 is t1’s first record; rec1!=NULL; rec1=rec1->next)
   for(rec2 in t2’s first record that match c1=d1; d1=c1; rec2=rec->next)
   {
         put  result(rec1.c1,rec1.c2,rec2.d1) into result set;
   }

  數據庫執行該執行語句的步驟也是類似的,下面是執行該執行語句的步驟:

  1) TAF(T1)(“TABLE Access (FULL) OF 'T1'”的簡寫)取得表T1的第一條記錄(1,1)傳遞給NL(“NESTED LOOPS”的簡寫),將控制權傳遞給NL。
  2) 操作符NL將控制權傳遞給第二個孩子IRS(IT2D1)(“INDEX (RANGE SCAN) OF 'IT2D1'”的簡寫)。
  3) IRS(IT2D1)使用鍵值(1)去命中索引IT2D1對應的B樹,得到索引記錄(1,rowid1)。將d1對應的數據(1)傳遞給NL,將控制權傳遞給NL。注意,在本例中,將d1的數據上傳是因為select中出現了d1,也就是說要將d1的值傳給客戶端,如果select中沒有d1,此處就和上例中是一樣的,不需要傳遞d1給上層。
  4) 操作NL組合生成記錄(1,1,1)(對應select項(c1,c2,d1))傳給SS,將控制權傳給SS。
  5) 操作符SS將記錄(1,1,1)放入結果集,將控制權傳給NL。
  6) NL將控制權傳給IRS(IT2D1)。此處傳給IRS(IT2D1)的原因是,it2d1是非唯一索引,可能有兩條以上的記錄符合d1=1。
  7) IRS(IT2D1)取得下一條記錄(2,rowid2),因為2!=1,所以對應d1=1的索引查找已經結束,通知NL,將控制權限傳遞給NL。
  8) NL控制權傳給TAF(T1)。
  9) TAF(T1)取得下一條記錄(2,2)傳遞給NL,將控制權傳給NL。
  10) NL將控制權傳給IRS(IT2D1)。
  11) IRS(IT2D1)使用鍵值(2)去命中索引IT2D1對應的B樹,得到索引記錄(2,rowid2)。將d1對應的數據(2)傳遞給NL,將控制權傳遞給NL。
  12) 操作NL組合生成記錄(2,2,2)傳給SS,將控制權傳給SS。
  13) 操作符SS將記錄(2,2,2)放入結果集,將控制權傳給NL。
  14) NL將控制權傳給IRS(IT2D1)。
  15) IRS(IT2D1)取得下一條記錄(3,rowid3),因為3!=2,所以對應d1=2的索引查找已經結束,通知NL查找結束,將控制權限傳遞給NL。
  16) NL控制權傳給TAF(T1)。
  17) TAF(T1)取得下一條記錄,發現已經掃描結束,通知NL掃描結束,將控制權傳給NL。
  18) NL通知SS掃描結束,將控制權傳給SS。
  19) SS將結果集(包含記錄(1,1,1)、(2,2,2))發送給客戶端。

  3.使用唯一索引的嵌套連接的執行計劃該如何理解?

  測試數據與1中描述的一樣。刪除原來的非唯一索引,建立唯一索引:

drop index it2d1;
create unique index iut2d1 on t2(d1);

  查詢語句:

select /*+ index(t2) */ c1,c2,d1 from t1 inner join t2 on c1=d1;

  對應的執行計劃:

  0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=2 Bytes=78)
   1    0   NESTED LOOPS (Cost=2 Card=2 Bytes=78)
   2    1     TABLE Access (FULL) OF 'T1' (TABLE) (Cost=2 Card=2 Bytes
          =52)
   3    1     INDEX (UNIQUE SCAN) OF 'IUT2D1' (INDEX (UNIQUE)) (Cost=0
           Card=1 Bytes=13)

  數據庫執行該執行語句的步驟也是類似的,下面是執行該執行語句的步驟:

  1) TAF(T1)(“TABLE Access (FULL) OF 'T1'”的簡寫)取得表T1的第一條記錄(1,1)傳遞給NL(“NESTED LOOPS”的簡寫),將控制權傳遞給NL。
  2) 操作符NL將控制權傳遞給第二個孩子IUS(IUT2D1)(“INDEX (UNIQUE SCAN) OF 'IUT2D1''”的簡寫)。
  3) IUS(IUT2D1)使用鍵值(1)去命中索引IUT2D1對應的B樹,得到索引記錄(1,rowid1)。將d1對應的數據(1)傳遞給NL,將控制權傳遞給NL。
  4) 操作NL組合生成記錄(1,1,1)(對應select項(c1,c2,d1))傳給SS,將控制權傳給SS。
  5) 操作符SS將記錄(1,1,1)放入結果集,將控制權傳給NL。
  6) NL控制權傳給TAF(T1)。因為iut2d1是唯一索引,所以只可能有一條記錄滿足d1=1,所以此時不需要將控制權限再傳給IUS(IUT2D1)。
  7) TAF(T1)取得下一條記錄(2,2)傳遞給NL,將控制權傳給NL。
  8) NL將控制權傳給IRS(IUT2D1)。
  9) IUS(IUT2D1)使用鍵值(2)去命中索引IUT2D1對應的B樹,得到索引記錄(2,rowid2)。將d1對應的數據(2)傳遞給NL,將控制權傳遞給NL。
  10) 操作NL組合生成記錄(2,2,2)傳給SS,將控制權傳給SS。
  11) 操作符SS將記錄(2,2,2)放入結果集,將控制權傳給NL。
  12) NL控制權傳給TAF(T1)。
  13) TAF(T1)取得下一條記錄,發現已經掃描結束,通知NL掃描結束,將控制權傳給NL。
  14) NL通知SS掃描結束,將控制權傳給SS。
  15) SS將結果集(包含記錄(1,1,1)、(2,2,2))發送給客戶端。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved