常常有人把這三個hint搞混,主要是因為對三種重寫原理不清楚。特總結如下。(實驗環境為10204)
1. no_unnest, unnest
unnest我們稱為對子查詢展開,顧名思義,就是別讓子查詢孤單地嵌套(nest)在里面。
所以un_unnest雙重否定代表肯定,即讓子查詢不展開,讓它嵌套(nest)在里面。
現做一個簡單的實驗:
create table hao1 as select * from dba_objects;
create table hao2 as select * from dba_objects;
analyze table hao1 compute statistics;
analyze table hao2 compute statistics;
SQL> select hao1.object_id from hao1 where exists
2 (select 1 from hao2 where hao1.object_id=hao2.object_id*10);
1038 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2662903432
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 80 (3)| 00:00:01 |
|* 1 | HASH JOIN SEMI | | 1 | 8 | 80 (3)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HAO1 | 10662 | 42648 | 40 (3)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HAO2 | 10663 | 42652 | 40 (3)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("HAO1"."OBJECT_ID"="HAO2"."OBJECT_ID"*10)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
352 consistent gets
0 physical reads
0 redo size
18715 bytes sent via SQL*Net to client
1251 bytes received via SQL*Net from client
71 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1038 rows processed
這里子查詢自動展開(unnest),即HAO2和HAO1 hash join在一起。
接下來如果我們不希望HAO2展開,想先讓它單獨的執行完,然后再來和外部查詢進行一種叫做FILTER的操作。
那么我們加入hint no_unnest:
SQL> select hao1.object_id from hao1 where exists
2 (select /*+no_unnest*/ 1 from hao2 where hao1.object_id=hao2.object_id*10);
1038 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2565749733
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 10750 (1)| 00:01:48 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| HAO1 | 10662 | 42648 | 40 (3)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| HAO2 | 1 | 4 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "HAO2" "HAO2"
WHERE "HAO2"."OBJECT_ID"*10=:B1))
3 - filter("HAO2"."OBJECT_ID"*10=:B1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1369157 consistent gets
0 physical reads
0 redo size
18715 bytes sent via SQL*Net to client
1251 bytes received via SQL*Net from client
71 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1038 rows processed
這里HAO1和HAO2進行了一種FILTER操作,這個操作在《Cost Based Oracle Fundamental》此書第九章有介紹。他其實很像我們熟悉的neested loop,但它的獨特之處在於會維護一個hash table。
舉例,如果HAO1里取出object_id=1,那么對於HAO2來說即select 1 from hao2 where hao2.object_id*10=1,如果條件滿足,那么對於子查詢,輸入輸出對,即為(1(HAO1.object_id),1(常量))。
他存儲在hash table里,並且由於條件滿足,HAO1.object_id=1被放入結果集。
然后接着從HAO1取出object_id=2,如果子查詢依舊條件滿足,那么子查詢產生另一個輸入和輸出,即(2,1),被放入hash table里;並且HAO1.object_id=2被放入結果集。
接着假設HAO1里有重復的object_id,例如我們第三次從HAO1取出的object_id=2,那么由於我們對於子查詢來說,已經有輸入輸出對(2,1)在hash table里了,所以就不用去再次全表掃描HAO2了,ORACLE非常聰明地知道object_id=2是結果集。這里,filter和neested loop相比,省去了一次全表掃描HAO2。
這個hash table是有大小限制的,當被占滿的時候,后續新的HAO1.object_id的FILTER就類似neested loop了。
由此可見,從buffer gets層面上來看,FILTER是應該優於neested loop的,尤其當外部查詢需要傳遞給子查詢的輸入(此例中為HAO1.object_id)的distinct value非常小時,FILTER就會顯得更優。
即使在我這個例子中,HAO1.object_id的distinct value上萬,我對比了一下neested loop,FILTER仍然略優:
SQL> select /*+use_nl(hao1 hao2)*/ hao1.object_id from hao1,hao2 where hao1.object_id=hao2.object_id*10;
1038 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 251947914
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10663 | 85304 | 404K (2)| 01:07:23 |
| 1 | NESTED LOOPS | | 10663 | 85304 | 404K (2)| 01:07:23 |
| 2 | TABLE ACCESS FULL| HAO1 | 10662 | 42648 | 40 (3)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| HAO2 | 1 | 4 | 38 (3)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("HAO1"."OBJECT_ID"="HAO2"."OBJECT_ID"*10)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1503621 consistent gets
0 physical reads
0 redo size
18715 bytes sent via SQL*Net to client
1251 bytes received via SQL*Net from client
71 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1038 rows processed
FILTER的consistent gets是1369157,neested loop的consistent gets是1503621。
如果我們希望驗證我前面的結論,我們可以用distinct value較小的object_type來做個類似的對比試驗。
SQL> select hao1.object_id from hao1 where exists
2 (select /*+no_unnest*/ 1 from hao2 where hao1.object_type=hao2.object_type);
10662 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2565749733
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 288 | 3168 | 114 (1)| 00:00:02 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| HAO1 | 10662 | 114K| 40 (3)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| HAO2 | 2 | 14 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "HAO2" "HAO2"
WHERE "HAO2"."OBJECT_TYPE"=:B1))
3 - filter("HAO2"."OBJECT_TYPE"=:B1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
17012 consistent gets
0 physical reads
0 redo size
187491 bytes sent via SQL*Net to client
8302 bytes received via SQL*Net from client
712 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10662 rows processed
可見,同樣是HAO1和HAO2的全表掃描后的FILTER操作,卻因為傳給子查詢的輸入的distinct value的差別,兩者相差的consistent gets卻如此巨大,這跟neested loop是完全不一樣的。
當然,對於如此的兩個全表掃描的結果集,使用hash join是最佳方法。
SQL> select hao1.object_id from hao1 where exists
2 (select 1 from hao2 where hao1.object_type=hao2.object_type);
10662 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3371915275
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10662 | 187K| 81 (4)| 00:00:01 |
|* 1 | HASH JOIN RIGHT SEMI| | 10662 | 187K| 81 (4)| 00:00:01 |
| 2 | TABLE ACCESS FULL | HAO2 | 10663 | 74641 | 40 (3)| 00:00:01 |
| 3 | TABLE ACCESS FULL | HAO1 | 10662 | 114K| 40 (3)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("HAO1"."OBJECT_TYPE"="HAO2"."OBJECT_TYPE")
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
985 consistent gets
0 physical reads
0 redo size
187491 bytes sent via SQL*Net to client
8302 bytes received via SQL*Net from client
712 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10662 rows processed
所以,什么時候該用no_unnest使得子查詢能夠獨立的執行完畢之后再跟外圍的查詢做FILTER?
首先,子查詢的返回結果集應該較小,然后外圍查詢的輸入的distinct value也應該較小(例如object_type)。
2.push_subq
如果說no_unnest是為了讓子查詢不展開,獨立的完成,那么push_subq就是為了讓子查詢最先進行join。
所以,這個hint其實是控制的join的順序。
例如某次在生產庫中遇到的一個SQL,簡化一下然后模擬一下:
create table hao1 as select * from dba_objects;
create table hao2 as select * from dba_objects;
create table hao3 as select * from dba_objects;
create table hao4 as select * from dba_objects;
create index hao3idx on hao3(object_id);
(analyze all tables。)
select hao1.object_name from
hao1,hao2,hao4
where hao1.object_name like '%a%'
and hao1.object_id+hao2.object_id>50
and hao4.object_type=hao1.object_type
and 11 in
(SELECT hao3.object_id FROM hao3 WHERE hao1.object_id = hao3.object_id);
對於如上的SQL,其中hao3和hao1在子查詢中join,
很明顯,如果先讓hao1和hao3通過join,結果集估計只有一行,或者沒有。
但是,此時CBO做出的執行計划為:
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 89077 | 3131K| 2070M (1)|999:59:59 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 3234M| 108G| 289K (24)| 00:48:17 |
| 3 | TABLE ACCESS FULL | HAO4 | 36309 | 212K| 126 (3)| 00:00:02 |
| 4 | NESTED LOOPS | | 3296K| 94M| 224K (2)| 00:37:28 |
|* 5 | TABLE ACCESS FULL| HAO1 | 1816 | 47216 | 126 (3)| 00:00:02 |
|* 6 | TABLE ACCESS FULL| HAO2 | 1815 | 7260 | 124 (2)| 00:00:02 |
|* 7 | FILTER | | | | | |
|* 8 | INDEX RANGE SCAN | HAO3IDX | 1 | 4 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT /*+ */ 0 FROM "HAO3" "HAO3" WHERE 11=:B1
AND "HAO3"."OBJECT_ID"=11))
2 - access("HAO4"."OBJECT_TYPE"="HAO1"."OBJECT_TYPE")
5 - filter("HAO1"."OBJECT_NAME" LIKE '%a%')
6 - filter("HAO1"."OBJECT_ID"+"HAO2"."OBJECT_ID">50)
7 - filter(11=:B1)
8 - access("HAO3"."OBJECT_ID"=11)
由上可見,hao1和hao2,hao4先進行無窮無盡的join之后,最后才跟hao3 join,這是非常壞的plan。
於是,我們希望hao1和hao3所在子查詢先join,可以采用push_subq:
select /*+push_subq(@tmp)*/ hao1.object_name from
hao1,hao2,hao4
where hao1.object_name like '%a%'
and hao1.object_id+hao2.object_id>50
and hao4.object_type=hao1.object_type
and 11 in
(SELECT /*+QB_Name(tmp)*/ hao3.object_id FROM hao3 WHERE hao1.object_id = hao3.object_id);
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 161M| 5552M| 14749 (24)| 00:02:28 |
|* 1 | HASH JOIN | | 161M| 5552M| 14748 (24)| 00:02:28 |
| 2 | TABLE ACCESS FULL | HAO4 | 36309 | 212K| 126 (3)| 00:00:02 |
| 3 | NESTED LOOPS | | 164K| 4828K| 11386 (2)| 00:01:54 |
|* 4 | TABLE ACCESS FULL | HAO1 | 91 | 2366 | 126 (3)| 00:00:02 |
|* 5 | FILTER | | | | | |
|* 6 | INDEX RANGE SCAN| HAO3IDX | 1 | 4 | 1 (0)| 00:00:01 |
|* 7 | TABLE ACCESS FULL | HAO2 | 1815 | 7260 | 124 (2)| 00:00:02 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("HAO4"."OBJECT_TYPE"="HAO1"."OBJECT_TYPE")
4 - filter("HAO1"."OBJECT_NAME" LIKE '%a%' AND EXISTS (SELECT /*+
PUSH_SUBQ QB_NAME ("TMP") */ 0 FROM "HAO3" "HAO3" WHERE 11=:B1 AND
"HAO3"."OBJECT_ID"=11))
5 - filter(11=:B1)
6 - access("HAO3"."OBJECT_ID"=11)
7 - filter("HAO1"."OBJECT_ID"+"HAO2"."OBJECT_ID">50)
加上hint后,SQL會在1秒以內完成。
3.push_pred
在談到push_pred這個hint時,首先要搞清楚mergeable view和unmergeable view的區別。
這個在concept上有明確解釋:
Mergeable and Unmergeable ViewsThe optimizer can merge a view into a referencing query block when the view has one or more base tables, provided the view does not contain:
-
set operators (UNION, UNION ALL, INTERSECT, MINUS)
-
a CONNECT BY clause
-
a ROWNUM pseudocolumn
- aggregate functions (AVG, COUNT, MAX, MIN, SUM) in the select list
When a view contains one of the following structures, it can be merged into a referencing query block only if complex view merging is enabled (as described below):
-
a GROUP BY clause
- a DISTINCT operator in the select list
View merging is not possible for a view that has multiple base tables if it is on the right side of an outer join. If a view on the right side of an outer join has only one base table, however, the optimizer can use complex view merging even if an expression in the view can return a non-null value for a NULL. See "Views in Outer Joins" for more information.
這里在最后,我們發現一個unmergeable view的一種情況就是view在outer join的右側。
對於這種情況,我們熟知的merge hint也無效。
例如:
create or replace view haoview as
select hao1.* from hao1,hao2
where hao1.object_id=hao2.object_id;
那么對於這樣一個簡單的查詢,可見謂詞hao3.object_name=haoview.object_name被merge到了view中:
select hao3.object_name
from hao3,haoview
where hao3.object_name=haoview.object_name
and hao3.object_id=999;
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 129 (3)| 00:00:02 |
| 1 | NESTED LOOPS | | 1 | 44 | 129 (3)| 00:00:02 |
|* 2 | HASH JOIN | | 1 | 40 | 128 (3)| 00:00:02 |
| 3 | TABLE ACCESS BY INDEX ROWID| HAO3 | 1 | 20 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | HAO3IDX | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS FULL | HAO1 | 36311 | 709K| 125 (2)| 00:00:02 |
|* 6 | INDEX RANGE SCAN | HAO2IDX | 1 | 4 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("HAO3"."OBJECT_NAME"="HAO1"."OBJECT_NAME")
4 - access("HAO3"."OBJECT_ID"=999)
6 - access("HAO1"."OBJECT_ID"="HAO2"."OBJECT_ID")
接着,我把haoview放到outer join的右側,這是haoview就屬於unmergeable view了,優化器默認無法將謂詞merge進這個haoview中,於是就看到了haoview單獨先執行:
select hao3.object_name
from hao3,haoview
where hao3.object_name=haoview.object_name(+)
and hao3.object_id=999;
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 86 | 153 (5)| 00:00:02 |
|* 1 | HASH JOIN OUTER | | 1 | 86 | 153 (5)| 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID| HAO3 | 1 | 20 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | HAO3IDX | 1 | | 1 (0)| 00:00:01 |
| 4 | VIEW | HAOVIEW | 36309 | 2340K| 150 (4)| 00:00:02 |
|* 5 | HASH JOIN | | 36309 | 850K| 150 (4)| 00:00:02 |
| 6 | INDEX FAST FULL SCAN | HAO2IDX | 36309 | 141K| 22 (5)| 00:00:01 |
| 7 | TABLE ACCESS FULL | HAO1 | 36311 | 709K| 125 (2)| 00:00:02 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("HAO3"."OBJECT_NAME"="HAOVIEW"."OBJECT_NAME"(+))
3 - access("HAO3"."OBJECT_ID"=999)
5 - access("HAO1"."OBJECT_ID"="HAO2"."OBJECT_ID")
接着,我們來使用這里的hint push_pred強制優化器將謂詞merge進view中,可見到“VIEW PUSHED PREDICATE”:
select /*+push_pred(haoview)*/ hao3.object_name
from hao3,haoview
where hao3.object_name=haoview.object_name(+)
and hao3.object_id=999;
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 40 | 128 (2)| 00:00:02 |
| 1 | NESTED LOOPS OUTER | | 1 | 40 | 128 (2)| 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID| HAO3 | 1 | 36 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | HAO3IDX | 1 | | 1 (0)| 00:00:01 |
| 4 | VIEW PUSHED PREDICATE | HAOVIEW | 1 | 4 | 126 (2)| 00:00:02 |
| 5 | NESTED LOOPS | | 1 | 24 | 126 (2)| 00:00:02 |
|* 6 | TABLE ACCESS FULL | HAO1 | 1 | 20 | 125 (2)| 00:00:02 |
|* 7 | INDEX RANGE SCAN | HAO2IDX | 1 | 4 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("HAO3"."OBJECT_ID"=999)
6 - filter("HAO1"."OBJECT_NAME"="HAO3"."OBJECT_NAME")
7 - access("HAO1"."OBJECT_ID"="HAO2"."OBJECT_ID")
於是,會有同學問,那么merge hint能否有同樣的效果呢?答案是,對於這種unmergeable view來說,merge hint無效。
select /*+merge(haoview)*/ hao3.object_name
from hao3,haoview
where hao3.object_name=haoview.object_name(+)
and hao3.object_id=999;
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 86 | 153 (5)| 00:00:02 |
|* 1 | HASH JOIN OUTER | | 1 | 86 | 153 (5)| 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID| HAO3 | 1 | 20 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | HAO3IDX | 1 | | 1 (0)| 00:00:01 |
| 4 | VIEW | HAOVIEW | 36309 | 2340K| 150 (4)| 00:00:02 |
|* 5 | HASH JOIN | | 36309 | 850K| 150 (4)| 00:00:02 |
| 6 | INDEX FAST FULL SCAN | HAO2IDX | 36309 | 141K| 22 (5)| 00:00:01 |
| 7 | TABLE ACCESS FULL | HAO1 | 36311 | 709K| 125 (2)| 00:00:02 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("HAO3"."OBJECT_NAME"="HAOVIEW"."OBJECT_NAME"(+))
3 - access("HAO3"."OBJECT_ID"=999)
5 - access("HAO1"."OBJECT_ID"="HAO2"."OBJECT_ID")
可見,對於此種身處outger join右側的view來說,merge hint已經無能為力了。
綜上,對於大家比較容易混淆的三個hint:
no_unnest/unnest是針對子查詢是否展開的,push_subq是針對子查詢的連接順序的,push_pred則是針對unmergeable view使用外部查詢謂詞。
