反連接
?反連接(Anti Join)是一種特殊的連接類型,與內連接和外連接不同,Oracle數據庫里并沒有相關的
關鍵字可以在SQL文本中專門表示反連接,所以這里把它單獨拿出來說明。
為了方便說明反連接的含義,我們用“t1.x.anti=t2.y”來表示表T1和表T2做反連接,且T1是驅動表,T2
是被驅動表,反連接條件為t1.x=t2.y,這里“t1.x.anti=t2.y”的含義是只要表T2中有滿足條件t1.x=t2.y
的記錄存在,則表T1中滿足條件t1.x=t2.y的記錄就會被丟棄,最后返回的記錄就是表T1中那些不滿足
條件t1.x=t2.y的記錄。
當做子查詢展開時,Oracle經常會把那些外部where條件為NOT EXISTS,NOT IN或<>ALL的子查詢轉換成
對應的反連接。
我們來看如下的范例SQL1、2和3
SQL> select * from t1 where col2 not in (select col2 from t2);
SQL> select * from t1 where col2<>all (select col2 from t2);
SQL> select * from t1 where not exists (select 1 from t2 where col2=t1.col2);
現在表T1和t2在各自的連接列COL2上均沒有NULL值,在這種情況下范例SQL1、2、3實際上是等價的。
SQL> select * from t1 where col2 not in (select col2 from t2);COL1 C
---------- -3 CExecution Plan
----------------------------------------------------------
Plan hash value: 1275484728---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------1 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size590 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed
SQL> select * from t1 where col2<>all (select col2 from t2);COL1 C
---------- -3 CExecution Plan
----------------------------------------------------------
Plan hash value: 1275484728---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------1 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size590 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed
SQL> select * from t1 where not exists (select 1 from t2 where col2=t1.col2);COL1 C
---------- -3 CExecution Plan
----------------------------------------------------------
Plan hash value: 2706079091---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------1 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size590 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed
上述3個范例SQL的執行結果是一樣的,范例SQL1、2和范例SQL3的執行計劃中,ID=1的執行步驟的列Operation的值分別為“HASH JOIN ANTI NA”和“HASH JOIN ANTI”,雖然不是完全一樣,但它們都有關鍵字“ANTI”,這就說明Oracle在執行上述三個范例SQL時確實是在用反連接,即Oracle在執行時實際上是將他們轉換成了如下的等價反連接形式:
select t1.* from t1,t2 where t1.col2 anti=t2.col2;
這里表T1、T2在各自的連接列COL2上沒有NULL值,所以此時這三個范例SQL是等價的,但如果連接列COL2上有null值,則它們就不完全等價了。這種null值所帶來的影響又細分為兩種情況:
1、表T1的連接列COL2上出現了NULL值
SQL> insert into t1 values(4,null);1 row created.SQL> commit;Commit complete.
表T1中的記錄如下:
SQL> select * from t1;COL1 C
---------- -1 A2 B3 C4
分別執行SQL1、2、3
SQL> select * from t1 where col2 not in (select col2 from t2);COL1 C
---------- -3 CExecution Plan
----------------------------------------------------------
Plan hash value: 1275484728---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------1 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size590 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed
SQL> select * from t1 where col2 <>all (select col2 from t2);COL1 C
---------- -3 CExecution Plan
----------------------------------------------------------
Plan hash value: 1275484728---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------1 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size590 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed
SQL> select * from t1 where not exists (select 1 from t2 where col2=t1.col2);COL1 C
---------- -43 CExecution Plan
----------------------------------------------------------
Plan hash value: 2706079091---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------0 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size645 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)2 rows processed
SQL> delete from t1 where col1=4;1 row deleted.SQL> commit;Commit complete.
2、表T2的連接列COL2上出現了NULL值
SQL> insert into t2 values(null,'E2');1 row created.SQL> COMMIT;Commit complete. SQL> select * from t1;COL1 C
---------- -1 A2 B3 CSQL> select * from t2;C CO
- --
A A2
B B2
D D2E2
再次執行上述三個SQL
SQL> select * from t1 where col2 not in (select col2 from t2);no rows selectedExecution Plan
----------------------------------------------------------
Plan hash value: 1275484728---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------0 recursive calls0 db block gets10 consistent gets0 physical reads0 redo size398 bytes sent via SQL*Net to client512 bytes received via SQL*Net from client1 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)0 rows processed
SQL> select * from t1 where col2 <>all (select col2 from t2);no rows selectedExecution Plan
----------------------------------------------------------
Plan hash value: 1275484728---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------0 recursive calls0 db block gets10 consistent gets0 physical reads0 redo size398 bytes sent via SQL*Net to client512 bytes received via SQL*Net from client1 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)0 rows processed
SQL> select * from t1 where not exists (select 1 from t2 where col2=t1.col2);COL1 C
---------- -3 CExecution Plan
----------------------------------------------------------
Plan hash value: 2706079091---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN ANTI | | 1 | 7 | 7 (15)| 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")Statistics
----------------------------------------------------------0 recursive calls0 db block gets12 consistent gets0 physical reads0 redo size590 bytes sent via SQL*Net to client523 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed
SQL> delete from t2 where col3='E2';1 row deleted.SQL> COMMIT;Commit complete.
從上述測試中我們可以得出以下結論:
1)表T1、T2在各自的連接列COL2上一但有了NULL值,則范例SQL1、2、3就不完全等價了。
2)NOT IN和<>ALL對NULL值敏感,這意味著NOT IN后面的子查詢或者常量集合一旦有NULL值出現,則整個SQL的執行結果就會為NULL,即此時的執行結果將不包含任何記錄。
3)NOT EXISTS對NULL值不敏感,這意味著NULL值對NOT EXISTS的執行結果不會有什么影響。正是因為NOT IN和<>ALL對NULL值敏感,所以一旦相關的連接列上出現NULL值,此時Oracle如果還按照通常的反連接的處理邏輯來處理,得到的結果就不對了。
為了解決NOT IN和<>ALL對NULL值敏感的問題,Oracle推出了改良的反連接,這種反連接能夠處理NULL值,Oracle稱其為Null-Aware Anti Join。上述范例SQL1、2的執行計劃中,ID=1的執行步驟的列Operation的值為“HASH JOIN ANTI NA”,關鍵字NA就是Null-Aware的縮寫。Oracle就是想告訴我們,這里采用的不是普通的哈希反連接,而是改良后的,能夠處理NULL值的哈希反連接。
在Oracle 11g R2中,Oracle是否啟用Null-Aware Anti Join受隱含參數_OPTIMIZER_NULL_AWARE_ANTIJOIN控制,其默認值為TRUE,表示啟用Null-Aware Anti join。
如果我們把_OPTIMIZER_NULL_AWARE_ANTIJOIN的值修改為FALSE,則Oracle就不能再用Null-Aware Anti Join了,而又因為NOT IN對NULL值敏感,所以Oracle此時也不能用普通的反連接。
設置當前session中的_OPTIMIZER_NULL_AWARE_ANTIJOIN值為FALSE
alter session set "_OPTIMIZER_NULL_AWARE_ANTIJOIN"=false;
修改后執行范例SQL1:
從上述顯示內容可以看出,當我們把_OPTIMIZER_NULL_AWARE_ANTIJOIN的值修改為FALSE后,Oracle果然沒有走反連接(當然也不能走)。
這里Oracle選擇了走FILTER類型的執行計劃,FILTER類型的執行計劃實際上是一種改良的嵌套循環連接。