1. 程式人生 > >Oracle 優化器_表連線

Oracle 優化器_表連線

概述

   在寫SQL的時候,有時候涉及到的不僅只有一個表,這個時候,就需要表連線了。Oracle優化器處理SQL語句時,根據SQL語句,確定表的連線順序(誰是驅動表,誰是被驅動表及 哪個表先和哪個表做連結)、連線方法(下文有詳細介紹)及訪問單表的方法(是否走索引,及走哪個索引)。

型別

  表連線的型別分為兩種:內連線和外連線。表連線的型別不同,得到的結果也不相同。不同的SQL語句使用不同的表連線。演示表連線我們用兩個表t1和t2 。建表語句及資料如下:

-- 建表語句
CREATE TABLE T1(col1 number,col2 VARCHAR2(1));
CREATE TABLE T2(col2 VARCHAR2(1),col3 VARCHAR2(2));
-- 表1資料
INSERT INTO t1 VALUES(1,'A');
INSERT INTO t1 VALUES(2,'B');
INSERT INTO t1 VALUES(3,'C');
-- 表2 資料
INSERT INTO t2 VALUES('A','A2');
INSERT INTO t2 VALUES('B','B2');
INSERT INTO t2 VALUES('D','D2');

內連線

   內連線,表的連線只包含滿足條件的記錄,Oracle資料庫預設的連結方式,只要在SQL語句中沒有(+),這裡的(+)是Oracle特有的連線符號。或者 left outer join 、 right outer join 、full outer join 那麼SQL的連線型別就是內連線。

  內連線的寫法:

  1.Oracle自帶(最常用):

SELECT T1.COL1,T1.COL2,T2.COL3 FROM T1,T2 WHERE T1.COL2 = t2.col2;

    得到結果如下:

  

 

   2.標準SQL寫法:

SELECT T1.COL1,T1.COL2,T2.COL3 FROM T1 JOIN T2 ON (t1.col2= t2.col2);

  結果如下:

  

 

   3.標準SQL寫法2:(這裡需要注意的是SQL語句的紅色部分,col2 欄位前不能加表名)

SELECT T1.COL1,COL2,T2.COL3 FROM t1 join t2 using (col2);

  結果如下:

  

 

   可以看出,三種內連線的寫法得到的結果是一樣的。執行執行計劃,得出的執行計劃也是相同的 :

 Plan hash value: 1838229974
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |    30 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN         |      |     3 |    30 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     3 |    15 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     3 |    15 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("T1"."COL2"="T2"."COL2")

  關於join using 標準SQL中有一種特殊的join using ,寫法如下:

SELECT t1.col1,col2,t2.col3 FROM t1 NATURAL JOIN t2;

  它在當前表結構下,執行結果跟上面的SQL語句執行結果一樣,執行計劃也相同,但是在其他情況下就不一定相同了。因為它是取兩個表中所有表名相同的列進行內連線,有可能有的列僅僅是命名相同,但是我們並不希望按照欄位中內容進行過濾,那麼這樣寫的話,就有可能得到一個錯誤的結果。所以開發中並不常用。

外連線

   外連線是對內連線的擴充套件,執行時,除了將滿足內連線條件的值查出來之外,還會包含驅動表中所有不滿足連線條件的記錄。外連線分為:左外連線、右外連線、全連線三種。下面分別介紹下:

左外連線

  語法: 

目標表1 left outer join 目標表2 on (連線條件)
或
目標表1 left outer join 目標表2 using(連線列集合)

  SQL舉例:

SELECT t1.col1,t1.col2,t2.col3 from t1 left outer join t2 on (t1.col2 = t2.col2);
-- 或
SELECT t1.col1,col2,t2.col3 from t1 left outer join t2 using (col2);
-- 或
SELECT T1.COL1,T1.COL2,T2.COL3 FROM T1,T2 WHERE T1.COL2 = t2.col2(+);

  這裡的第三種寫法是Oracle特有的寫法。這三種寫法的執行結果一樣:

  

 

  執行計劃也相同:

Plan hash value: 1823443478
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |    60 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN OUTER   |      |     3 |    60 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     3 |    45 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     3 |    15 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("T1"."COL2"="T2"."COL2"(+))

   這裡因為表1作為驅動表,而表2中不包含col2 為C 的資料,所以在第三行結果中,col3 的值為null。

右外連結

  右外連結跟左外連線類似,語法為:

目標表1 right outer join 目標表2 on (連線條件)
或
目標表1 right outer join 目標表2 using(連線列集合)

  SQL 舉例:

SELECT t1.col1,t2.col2,t2.col3 from t1 right outer join t2 on (t2.col2 = t1.col2);
-- 或
SELECT t1.col1,col2,t2.col3 from t1 right outer join t2 using (col2);
-- 或
SELECT T1.COL1,T2.COL2,T2.COL3 FROM T1,T2 WHERE T1.COL2(+) = t2.col2;

  SQL查詢得到的結果和執行計劃也是一致的:

  

 

   執行計劃:

Plan hash value: 1426054487
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |    60 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN OUTER   |      |     3 |    60 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T2   |     3 |    15 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T1   |     3 |    45 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("T1"."COL2"(+)="T2"."COL2")

全連線

  語法為:

目標表1 full outer join 目標表2 on (連線條件)
或
目標表1 full outer join 目標表2 using(連線列集合)

  SQL舉例:

SELECT t1.col1,t1.col2,t2.col3 from t1 full outer join t2 on (t1.col2 = t2.col2);
-- 或
SELECT t1.col1,col2,t2.col3 from t1 full outer join t2 using (col2);

  執行結果為:

  

 

   因為第一個語句限定了col2段的值為t1表,所以第三條結果的col2 欄位為空。

   執行計劃相同:

Plan hash value: 53297166
 
----------------------------------------------------------------------------------
| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |          |     3 |    54 |     6   (0)| 00:00:01 |
|   1 |  VIEW                 | VW_FOJ_0 |     3 |    54 |     6   (0)| 00:00:01 |
|*  2 |   HASH JOIN FULL OUTER|          |     3 |    60 |     6   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL  | T1       |     3 |    45 |     3   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL  | T2       |     3 |    15 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("T1"."COL2"="T2"."COL2")
 

  全連線的效果為左連線+右連結,SQL如下:

SELECT t1.col1,t1.col2,t2.col3 from t1 right outer join t2 on (t1.col2 = t2.col2)
union
SELECT t1.col1,t1.col2,t2.col3 from t1 left outer join t2 on (t1.col2 = t2.col2);

  執行結果如下;

  

 

   可以看出得到的執行結果順序不同,內容一致。但是,執行的時候,資料庫的執行計劃並不是這樣的,下面是這條SQL的執行計劃,通過對比跟全連線SQL的執行計劃,可以看出,當前SQL是先查出左連線,再查出右連線,最後做了一個取並集的操作。而全連線是在取資料的時候,直接做的是取外連線的操作。

Plan hash value: 2747422401
 
-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     6 |   120 |    14  (15)| 00:00:01 |
|   1 |  SORT UNIQUE         |      |     6 |   120 |    14  (15)| 00:00:01 |
|   2 |   UNION-ALL          |      |       |       |            |          |
|*  3 |    HASH JOIN OUTER   |      |     3 |    60 |     6   (0)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| T2   |     3 |    15 |     3   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| T1   |     3 |    45 |     3   (0)| 00:00:01 |
|*  6 |    HASH JOIN OUTER   |      |     3 |    60 |     6   (0)| 00:00:01 |
|   7 |     TABLE ACCESS FULL| T1   |     3 |    45 |     3   (0)| 00:00:01 |
|   8 |     TABLE ACCESS FULL| T2   |     3 |    15 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("T1"."COL2"(+)="T2"."COL2")
   6 - access("T1"."COL2"="T2"."COL2"(+))
 

  ·特例:natural 在外連線中同樣適用,和內連線的方法和弊端也相同。

反連線

    見下文

半連線

    見下文

方法

  在Oracle優化器確定執行計劃中表連線的型別之後,就會決定表連線的方法。表連線的方法有四種:1.排序合併連線、2.巢狀迴圈連線、3.雜湊連線、4.笛卡爾積連線。下面我們分別介紹下這四種連線:

1.排序合併連線

  排序合併連線,兩個表做連線時,用排序和合並兩種操作來得到結果集的連線方法。

  sql舉例(當前SQL只是為了演示排序合併連線而寫的SQL,我還沒有想好具體的使用場景,歡迎大家給出):

select t1.col1 ,t1.col2,t2.col2,t2.col3  from t2,t1 where t1.col2>t2.col2

  執行結果:

  

 

 

   執行計劃:

Plan hash value: 412793182
 
----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     3 |    30 |     8  (25)| 00:00:01 |
|   1 |  MERGE JOIN         |      |     3 |    30 |     8  (25)| 00:00:01 |
|   2 |   SORT JOIN         |      |     3 |    15 |     4  (25)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| T2   |     3 |    15 |     3   (0)| 00:00:01 |
|*  4 |   SORT JOIN         |      |     3 |    15 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL| T1   |     3 |    15 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access(INTERNAL_FUNCTION("T1"."COL2")>INTERNAL_FUNCTION("T2"."COL
              2"))
       filter(INTERNAL_FUNCTION("T1"."COL2")>INTERNAL_FUNCTION("T2"."COL
              2"))
 

  通過執行計劃可以看出,排序合併連線執行過程為(先執行t1還是t2的排序跟具體情況相關,當前SQL的執行順序取決於from 關鍵字後 t1和t2的位置):

   ①首先對t1表中的資料按照where條件中的連線列來進行排序(Id 為4 那行的 SORT  操作),排序得到結果集1;

   ②然後對t2 執行類似的操作(Id 為2 那行的 SORT  操作)。

   ③最後,對兩個結果集進行合併操作(Id為 1 的行的 MERGE 操作),將滿足條件的記錄作為最終結果。

2.巢狀迴圈連線

  迴圈巢狀連線,兩個表做連線的時候,靠兩層巢狀迴圈(內迴圈和外迴圈)來得到連線結果的表連線方法。

  舉例:

-- 首先,建立一個索引在t2表
   create index idx_t2 on t2(col2);
-- 然後執行SQL
   select /*+ ordered use_n1(t2)*/ t1.col1 ,t1.col2,t2.col3 from t1,t2 where t1.col2= t2.col2

  得到執行結果:

Plan hash value: 1054738919
 
---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |     3 |    30 |     6   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |        |     3 |    30 |     6   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |        |     3 |    30 |     6   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | T1     |     3 |    15 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | IDX_T2 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |     5 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access("T1"."COL2"="T2"."COL2")
 

  其執行過程類似於兩個for迴圈的感覺 :

  ①首先,確定哪個表做外迴圈,哪個表做內迴圈。

  ②然後,訪問外迴圈的表資料,得到結果集1;

  ③最後,遍歷結果集1中的每一條記錄,然後每條記錄作為匹配條件去遍歷一遍表2去檢視是否有存在匹配的資料。得到返回的資料。

  總結:

  對於巢狀迴圈連線,外迴圈中有多少條記錄,內迴圈就要做多少次遍歷資料的操作。適用於外迴圈的結果集較少,同時內迴圈建立有索引,且索引選擇率較高的場景。

  巢狀迴圈可以快速響應,它可以第一時間返回滿足條件的記錄,而不用等整個迴圈執行完畢。

3.雜湊連線

  雜湊連結可以簡單理解為(t1,t2表為例):

  ①根據謂語條件判斷兩個表哪個表的結果集較小就作為驅動表。

  ②根據驅動表的列,計算出一個雜湊值,進行快取。

  ③計算被驅動表,每一行對應列的雜湊值,判斷是否在快取中存在該雜湊值,如果存在,進一步判斷是否對應列內容一致。

  ⑤將對應的內容輸出。

  雜湊連結實際情況要更加複雜,這裡只是大概介紹下。

4.笛卡爾連線

  兩個表的積成,在兩個表做連線時,沒有任何連線條件時的表連線方法。

  SQL舉例:

 select t1.col1 ,t1.col2,t2.col3 from t1,t2

  執行結果:

  

  執行計劃:

Plan hash value: 787647388
 
-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     9 |    72 |     9   (0)| 00:00:01 |
|   1 |  MERGE JOIN CARTESIAN|      |     9 |    72 |     9   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL  | T1   |     3 |    15 |     3   (0)| 00:00:01 |
|   3 |   BUFFER SORT        |      |     3 |     9 |     6   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL | T2   |     3 |     9 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------
 
Note

  執行步驟:

  ①訪問表t1,得到結果集1

  ②訪問表t2,得到結果集2

  ③對結果集1和結果集2做合併操作。因為沒有表連線條件,所以每一條結果集2都可以合併結果集1中的每一條資料,所以得到的結果集總數為:t1錶行數* t2錶行數

  注:笛卡爾積一般都是因為SQL語句中where條件漏寫導致的。笛卡爾積中兩表資料很大那麼SQL效率會受到嚴重影響。

反連線

   反連線,一種特殊連線型別。查詢出表1中不等於表2中某些欄位資料的值。

  SQL舉例:

-- not  in
select * from t1 where col2 not in (select col2 from t2);

-- <> all
select * from t1 where col2 <> all (select col2 from t2);

-- not exists

select * from t1 where not exists (select 1 from t2 where col2= t1.col2);

  執行計劃分別為:

-- not  in

Plan hash value: 1275484728
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     7 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN ANTI NA |      |     1 |     7 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     3 |    15 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     3 |     6 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("COL2"="COL2")
 
Note
-- <> all
Plan hash value: 1275484728
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     7 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN ANTI NA |      |     1 |     7 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     3 |    15 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     3 |     6 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("COL2"="COL2")
 
-- not exists

Plan hash value: 2706079091
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     7 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN ANTI    |      |     1 |     7 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     3 |    15 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     3 |     6 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("COL2"="T1"."COL2")

  對比執行計劃,可以看出,not in 和 <> all 的寫法,執行計劃完全一樣,雖然 not exists 的寫法的執行計劃id為1的行沒有NA的字元。但是他們都有 HASH JOIN ANTI 的關鍵字。說明Oracle都將他們轉化為了 如下的等價形式:

select t1.* from t1,t2 where t1.col2 anti= t2.col2;

  這裡的NA的區別為:當表字段中出現null值後,兩者的返回結果就不一致了。

  在t1表新增一行:

INSERT into t1 VALUES (4,null);
commit;

  執行結果:

  not in 和 not exists  

  

 

 

   not exists

  

   這是因為,前兩個對null敏感,一旦遇到null,則當前行記錄直接認為不符合。而not exists 對null不敏感,將null作為普通資料處理。

半連線

   半連線的關鍵詞為:in any exists ;

  SQL舉例:

-- in 
select * from t1 where col2 in (select col2 from t2);

-- any

select * from t1 where col2 = any (select col2 from t2);

--exists

select * from t1 where exists (select 1 from t2 where col2= t1.col2);

  執行結果都是
  

  半連線邏輯:

  ①掃描t1,得到結果集1

  ②根據結果集1的每條記錄掃描t2,只要在t2中找到符合條件的結果,那麼立馬停止掃描t2,將結果放入待返回的結果集

  ③ 返回結果集

 

 

注:當前部落格Oracle 版本為11g,其他版本執行計劃可能不一