Oracle三組難纏的hint no_unnest/unnest,push_subq,push_pred

Oracle三組難纏的hint no_unnest/unnest,push_subq,push_pred
2012-02-24


 </body>

友情使歡欣倍增,使疾苦減半。經常有人把這三個hint搞混,主如果因爲對三種重寫道理不清楚。特總結如下。(實驗景象爲10204)
1. no_unnest, unnest
unnest我們稱爲對子查詢展開,顧名思義,就是別讓子查詢孤單地嵌套(nest)在裏面。
所以un_unnest雙重否定代表必然,即讓子查詢不展開,讓它嵌套(nest)在裏面。
現做一個簡單的實驗:
create table hao1 as * dba_objects;
create table hao2 as * dba_objects;

analyze table hao1 compute statistics;
analyze table hao2 compute statistics;
SQL> hao1.object_id hao1 where exists
2 ( 1 hao2 where hao1.object_id=hao2.object_id*10);

1038 rows ed.

ution 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 client
71 SQL*Net roundtrips to/ client
0 sorts (memory)
0 sorts (disk)
1038 rows processed


這裏子查詢主動展開(unnest),即HAO2和HAO1 hash join在一路。
接下來若是我們不HAO2展開,想先讓它零丁的履行完,然後再來和外部查詢進行一種叫做FILTER的操縱。
那麼我們參加hint no_unnest:
SQL> hao1.object_id hao1 where exists
2 ( /*+no_unnest*/ 1 hao2 where hao1.object_id=hao2.object_id*10);

1038 rows ed.

ution 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 client
71 SQL*Net roundtrips to/ 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來說即 1 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> /*+use_nl(hao1 hao2)*/ hao1.object_id hao1,hao2 where hao1.object_id=hao2.object_id*10;

1038 rows ed.

ution 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 client
71 SQL*Net roundtrips to/ client
0 sorts (memory)
0 sorts (disk)
1038 rows processed

FILTER的consistent gets是1369157,neested loop的consistent gets是1503621。
若是我們驗證我前面的結論,我們可以用distinct value較小的object_type來做個類似的對比實驗。
SQL> hao1.object_id hao1 where exists
2 ( /*+no_unnest*/ 1 hao2 where hao1.object_type=hao2.object_type);

10662 rows ed.

ution 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 client
712 SQL*Net roundtrips to/ client
0 sorts (memory)
0 sorts (disk)
10662 rows processed

可見,同樣是HAO1和HAO2的全表掃描後的FILTER操縱,卻因爲傳給子查詢的輸入的distinct value的差別,兩者相差的consistent gets卻如此重大,這跟neested loop是完全不一樣的。
當然,對於如此的兩個全表掃描的成果集,應用hash join是最佳辦法。
SQL> hao1.object_id hao1 where exists
2 ( 1 hao2 where hao1.object_type=hao2.object_type);

10662 rows ed.

ution 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 client
712 SQL*Net roundtrips to/ 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 * dba_objects;
create table hao2 as * dba_objects;
create table hao3 as * dba_objects;
create table hao4 as * dba_objects;

create index hao3idx on hao3(object_id);
(analyze all tables。)
hao1.object_name
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:
/*+push_subq(@tmp)*/ hao1.object_name
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 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 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
hao1.* hao1,hao2
where hao1.object_id=hao2.object_id;

那麼對於如許一個簡單的查詢,可見謂詞hao3.object_name=haoview.object_name被merge到了view中:
hao3.object_name
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零丁先履行:
hao3.object_name
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”:
/*+push_pred(haoview)*/ hao3.object_name
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無效。
/*+merge(haoview)*/ hao3.object_name
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應用外部查詢謂詞。

發佈了15 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章