Oracle優化器內部處理的表連線方式

yuan22003發表於2011-08-23

表的連線是指在一個SQL語句中通過表與表之間的關聯,從一個或多個表檢索出相關的資料。連線是通過SQL語句中FROM從句的多個表名,以及WHERE從句裡定義的表之間的連線條件來實現的。如果一個SQL語句的關聯表超過兩個,那麼連線的順序如何呢?ORACLE首先連線其中的兩個表,產生一個結果集;然後將產生的結果集與下一個表再進行關聯;繼續這個過程,直到所有的表都連線完成;最後產生所需的資料。下面都以兩個表的連線為例

create table user_info(user_name char(10),user_id char(10));

create table dev_info(dev_no char(10),user_id char(10),dev_type char(10));

說明和分析表的各種連線方式。

ORACLE6的版本開始,優化器使用4種不同的表的連線方式

Ø巢狀迴圈連線(NESTED LOOP JOIN

Ø群集連線(CLUSTER JOIN)

Ø排序合併連線(SORT MERGE JOIN

Ø笛卡爾連線(CARTESIAN JOIN)

ORACLE 7.3中,新增加了

Ø雜湊連線(HASH JOIN)

ORACLE 8中,新增加了

Ø索引連線(INDEX JOIN)

這六種連線方式都有其獨特的技術特點,在一定的條件下,可以充分發揮高效的效能。

但是也都有其侷限性,如果使用不當,不僅不能提高效率,反而會嚴重影響系統的效能。因此,深入地探討連線方式的內部執行機制對於效能優化是必要的。

1巢狀迴圈連線

巢狀迴圈連線的內部處理的流程:

1)Oracle優化器根據基於規則RBO或基於成本CBO的原則,選擇兩個表中的一個作為驅動表,並指定其為外部表。

2)Oracle優化器再將另外一個表指定為內部表。

3)Oracle從外部表中讀取第一行,然後和內部表中的資料逐一進行對比,所有匹配的記錄放在結果集中。

4)Oracle讀取外部表中的第二行,再和內部表中的資料逐一進行對比,所有匹配的記錄新增到結果集中。

5)重複上述步驟,直到外部表中的所有紀錄全部處理完。

6)最後產生滿足要求的結果集。

通過查詢SQL語句的執行計劃可以看出哪個表是外部表,哪個為內部表。

select a.user_name,b.dev_no

from user_info a, dev_info b

where a.user_id = b.user_id;

上面的表是外部表,即驅動表

下面的表是內部表

的執行計劃:

SELECT STATEMENT Optimizer=CHOOSE

NESTED LOOPS

TABLE ACCESS (FULL) OF 'USER_INFO'

TABLE ACCESS (FULL) OF 'DEV_INFO'

使用巢狀迴圈連線是一種從結果集中提取第一批記錄最快速的方法。在驅動行源表(就是正在查詢的記錄)較小、或者內部行源表已連線的列有惟一的索引或高度可選的非惟一索引時,巢狀迴圈連線效果是比較理想的。巢狀迴圈連線比其他連線方法有優勢,它可以快速地從結果集中提取第一批記錄,而不用等待整個結果集完全確定下來。這樣,在理想情況下,終端使用者就可以通過查詢螢幕檢視第一批記錄,而在同時讀取其他記錄。不管如何定義連線的條件或者模式,任何兩行記錄源可以使用巢狀迴圈連線,所以巢狀迴圈連線是非常靈活的。

然而,如果內部行源表(讀取的第二張表)已連線的列上不包含索引,或者索引不是高度可選時,巢狀迴圈連線效率是很低的。如果驅動表的記錄非常龐大時,其他的連線方法可能更加有效。

可以通過在SQL語句中新增HINTS,強制ORACLE優化器產生巢狀迴圈連線的執行計劃。

select /*+ use_nl(a b) */ a.user_name,b.dev_no

from user_info a, dev_info b

where a.user_id = b.user_id;

2群集連線(CLUSTER JOIN

群集連線實際上是巢狀迴圈連線的一種特例。如果所連線的兩張源表是群集中的表,即兩張表屬於同一個段(SEGMENT,,那麼ORACLE能夠使用群集連線。處理的過程是:ORACLE從第一張行源表中讀取第一行,然後在第二張行源表中使用CLUSTER索引查詢能夠匹配到的紀錄;繼續上面的步驟處理行源表中的第二行,直到所有的記錄全部處理完。

群集連線的效率極高,因為兩個參加連線的行源表實際上處於同一個物理塊上。但是,群集連線也有其限制,沒有群集的兩個表不可能用群集連線。所以,群集連線實際上很少使用

3排序合併連線(SORT MERGE JOIN

排序合併連線內部處理的流程:

1)優化器判斷第一個源表是否已經排序,如果已經排序,則到第3步,否則

到第2步。

2)第一個源表排序

3)優化器判斷第二個源表是否已經排序,如果已經排序,則到第5步,否則

到第4步。

4)第二個源表排序

5)已經排過序的兩個源表進行合併操作,並生成最終的結果集。

在缺乏資料的選擇性或者可用的索引時,或者兩個源表都過於龐大(所選的資料超過表記錄數的5%),排序合併連線將比巢狀迴圈連更加高效

排列合併連線需要比較大的臨時記憶體塊,以用於排序,這將導致在臨時表空間佔用更多的記憶體和磁碟I/O

select a.user_name,b.dev_no

from user_info a, dev_info b

where a.user_id > b.user_id;

Plan

--------------------------------------------------

SELECT STATEMENT Optimizer=CHOOSE (Cost=7 Card=336 Bytes=16128)

MERGE JOIN (Cost=7 Card=336 Bytes=16128)

SORT (JOIN) (Cost=4 Card=82 Bytes=1968)

TABLE ACCESS (FULL) OF 'USER_INFO' (Cost=2 Card=82 Bytes=1968)

SORT (JOIN) (Cost=4 Card=82 Bytes=1968)

TABLE ACCESS (FULL) OF 'DEV_INFO' (Cost=2 Card=82 Bytes=1968)

可以通過在SQL語句中新增HINTS,強制ORACLE優化器產生排序合併連線的執行計劃。

select /*+ use_merge(a b) */ a.user_name,b.dev_no

from user_info a, dev_info b

where a.user_id > b.user_id;

排序合併連線是基於RBO的。

4笛卡爾連線(CARTESIAN JOIN)

笛卡爾連線是指在sql語句中沒有寫出表連線的條件,優化器把第一個表的每一條記錄和第二個表的所有紀錄相連線。如果第一個表的紀錄數為m,第二個表的紀錄數為m,則會產生m*n條紀錄數。

下面的查詢,未指名連線條件,就會產生笛卡爾連線。

select a.user_name,b.dev_no

from user_info a ,dev_info b;

由於笛卡爾連線會導致效能很差的SQL,因此一般也很少用到

5雜湊連線

當記憶體能夠提供足夠的空間時,雜湊(HASH)連線是Oracle優化器通常的選擇。雜湊連線中,優化器根據統計資訊,首先選擇兩個表中的小表,在記憶體中建立這張表的基於連線鍵的雜湊表;優化器再掃描表連線中的大表,將大表中的資料與雜湊表進行比較,如果有相關聯的資料,則將資料新增到結果集中。

當表連線中的小表能夠完全cache到可用記憶體的時候,雜湊連線的效果最佳。雜湊連線的成本只是兩個表從硬碟讀入到記憶體的成本。

但是,如果雜湊表過大而不能全部cache到可用記憶體時,優化器將會把雜湊表分成多個分割槽,再將分割槽逐一cache到記憶體中。當表的分割槽超過了可用記憶體時,分割槽的部分資料就會臨時地寫到磁碟上的臨時表空間上。因此,分割槽的資料寫磁碟時,比較大的區間(EXTENT)會提高I/O效能。ORACLE推薦的臨時表空間的區間是1MB。臨時表空間的區間大小由UNIFORM SIZE指定。

當雜湊表構建完成後,進行下面的處理:

1)第二個大表進行掃描

2)如果大表不能完全cache到可用記憶體的時候,大表同樣會分成很多分割槽

3)大表的第一個分割槽cache到記憶體

4)對大表第一個分割槽的資料進行掃描,並與雜湊表進行比較,如果有匹配的紀錄,新增到結果集裡面

5)與第一個分割槽一樣,其它的分割槽也類似處理。

6)所有的分割槽處理完後,ORACLE對產生的結果集進行歸併,彙總,產生最終的結果。

當雜湊表過大或可用記憶體有限,雜湊表不能完全CACHE到記憶體。隨著滿足連線條件的結果集的增加,可用記憶體會隨之下降,這時已經CACHE到記憶體的資料可能會重新寫回到硬碟去。如果出現這種情況,系統的效能就會下降。

當連線的兩個表是用等值連線並且表的資料量比較大時,優化器才可能採用雜湊連線。雜湊連線是基於CBO的。只有在資料庫初始化引數HASH_JOIN_ENABLED設為True,並且為引數PGA_AGGREGATE_TARGET設定了一個足夠大的值的時候,Oracle才會使用雜湊邊連線。HASH_AREA_SIZE是向下相容的引數,但在Oracle9i之前的版本中應當使用HASH_AREA_SIZE當使用ORDERED提示時,FROM子句中的第一張表將用於建立雜湊表。

selecta.user_name,b.dev_no

from user_info a, dev_info b

where a.user_id = b.user_id;

Plan

----------------------------------------------------------

0SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=82 Bytes=3936

)

10 HASH JOIN (Cost=5 Card=82 Bytes=3936)

21 TABLE ACCESS (FULL) OF 'USER_INFO' (Cost=2 Card=82 Bytes

=1968)

31 TABLE ACCESS (FULL) OF 'DEV_INFO' (Cost=2 Card=82 Bytes=

1968)

可以通過在SQL語句中新增HINTS,強制ORACLE優化器產生雜湊連線的執行計劃。

select /*+ use_hash(a b)*/ a.user_name,b.dev_no

from user_info a, dev_info b

where a.user_id = b.user_id;

當缺少有用的索引時,雜湊連線比巢狀迴圈連線更加有效。雜湊連線也可能比巢狀迴圈連線更快,因為處理記憶體中的雜湊表比檢索B_樹索引更加迅速。

6索引連線

如果一組已存在的索引包含了查詢所需要的所有資訊,那麼優化器將在索引中有選擇地生成一組雜湊表。可通過範圍或者快速全域性掃描訪問到每一個索引,而選擇何種掃描方式取決於WHERE子句中的可有條件。在一張表有大量的列,而您只想訪問有限的列時,這種方法非常有效。WHERE子句約束條件越多,執行速度越快。因為優化器在評估執行查詢的優化路徑時,將把約束條件作為選項看待。您必須在合適的列(那些滿足整個查詢的列)上建立索引,這樣可以確保優化器將索引連線作為可選項之一。這個任務通常牽涉到在沒有索引,或者以前沒有建立聯合索引的列上增加索引。相對於快速全域性掃描,連線索引的優勢在於:快速全域性掃描只有一個單一索引滿足整個查詢;索引連線可以有多個索引滿足整個查詢。

假設表dev_info上有兩個索(一個在dev_no,一個在dev_type上)。

作如下的查詢

selectdev_no,dev_type

from user_info

whereuser_id = ‘U101010’

anddev_type = ‘1010’;

幾種主要表連線的比較

類別

巢狀迴圈連線

排序合併連線

雜湊連線

優化器提示

USE_NL

USE_MERGE

USE_HASH

使用的條件

任何連線

主要用於不等價連線,如< <= > >=;

但是不包括 <>

僅用於等價連線

相關資源

CPU、磁碟I/O

記憶體、臨時空間

記憶體、臨時空間

特點

當有高選擇性索引或進行限制性搜尋時效率比較高,能夠快速返回第一次的搜尋結果。

當缺乏索引或者索引條件模糊時,排序合併連線比巢狀迴圈有效。

當缺乏索引或者索引條件模糊時,雜湊連線連線比巢狀迴圈有效。通常比排序合併連線快。

在資料倉儲環境下,如果表的紀錄數多,效率高。

缺點

當索引丟失或者查詢條件限制不夠時,效率很低;

當表的紀錄數多時,效率低。

所有的表都需要排序。它為最優化的吞吐量而設計,並且在結果沒有全部找到前不返回資料。

為建立雜湊表,需要大量記憶體。第一次的結果返回較慢。

結束語

深入地理解和掌握oracle的表連線對於優化資料庫的效能至關重要。由於優化器選擇方式的不同,以及統計資訊的缺失或統計資訊的不準確,ORACLE自動選擇的表連線方式不一定是最優的。當SQL語句的執行效率很低時,可通過auto trace對執行計劃進行跟蹤和分析。當出現多表連線時,需要仔細分析是否有更佳的連線條件。根據系統的特點,必要時可以在SQL中新增HINTS,從而改變SQL的執行計劃,從而達到效能優化的目的。


相關文章