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,其他版本執行計劃可能不一