1. 程式人生 > >hash join (Oracle裡的雜湊連線原理)

hash join (Oracle裡的雜湊連線原理)

雜湊連線(HASH JOIN)是一種兩個表在做表連線時主要依靠雜湊運算來得到連線結果集的表連線方法。

在Oracle 7.3之前,Oracle資料庫中的常用表連線方法就只有排序合併連線和巢狀迴圈連線這兩種,但這兩種表連線方法都有其明顯缺陷。對於排序合併連線,如果兩個表在施加了目標SQL中指定的謂詞條件(如果有的話)後得到的結果集很大且需要排序的話,則這種情況下的排序合併連線的執行效率一定是很差的;而對於巢狀迴圈連線,如果驅動表所對應的驅動結果集的記錄數很大,即便在被驅動表的連線列上存在索引,此時使用巢狀迴圈連線的執行效率也同樣會很差。

為了解決排序合併連線和巢狀迴圈連線在上述情形下執行效率不高的問題,同時也為了給優化器提供一種新的選擇,Oracle在Oracle 7.3中引入了雜湊連線。從理論上來說,雜湊連線的執行效率會比排序合併連線和巢狀迴圈連線的執行效率要高,當然,實際情況並不總是這樣。

在Oracle 10g及其以後的Oracle資料庫版本中,優化器(實際上是CBO,因為雜湊連線僅適用於CBO)在解析目標SQL時是否考慮雜湊連線是受限於隱含引數_HASH_JOIN_ENABLED,而在Oracle 10g以前的Oracle資料庫版本中,CBO在解析目標SQL時是否考慮雜湊連線是受限於引數HASH_JOIN_ENABLED。

_HASH_JOIN_ENABLED的預設值是TRUE,表示允許CBO在解析目標SQL時考慮雜湊連線。當然,即使你將該引數的值改成了FALSE,我們使用USE_HASH Hint依然可以讓CBO在解析目標SQL時考慮雜湊連線,這說明USE_HASH Hint的優先順序高於引數_HASH_JOIN_ENABLED。

   

如果兩個表(這裡將它們分別命名為表T1和表T2)在做表連線時使用的是雜湊連線,則Oracle在做雜湊連線時會依次順序執行如下步驟:

1、  首先Oracle會根據引數HASH_AREA_SIZE、DB_BLOCK_SIZE和_HASH_MULTIBLOCK_IO_COUNT的值來決定Hash Partition的數量(Hash Partition是一個邏輯上的概念,所有Hash Partition的集合就被稱之為Hash Table,即一個Hash Table是由多個Hash Partition所組成,而一個Hash Partition又是由多個Hash Bucket所組成);

2、  表T1和T2在施加了目標SQL中指定的謂詞條件(如果有的話)後得到的結果集中資料量較小的那個結果集會被Oracle選為雜湊連線的驅動結果集,這裡我們假設T1所對應的結果集的資料量相對較小,我們記為S;T2所對應的結果集的資料量相對較大,我們記為B;顯然這裡S是驅動結果集,B是被驅動結果集;

3、  接著Oracle會遍歷S,讀取S中的每一條記錄,並對S中的每一條記錄按照該記錄在表T1中的連線列做雜湊運算,這個雜湊運算會使用兩個內建雜湊函式,這兩個雜湊函式會同時對該連線列計算雜湊值,我們把這兩個內建雜湊函式分別記為hash_func_1和hash_func_2,它們所計算出來的雜湊值分別記為hash_value_1和hash_value_2;

4、  然後Oracle會按照hash_value_1的值把相應的S中的對應記錄儲存在不同Hash Partition的不同Hash Bucket裡,同時和該記錄儲存在一起的還有該記錄用hash_func_2計算出來的hash_value_2的值。注意,儲存在Hash Bucket裡的記錄並不是目標表的完整行記錄,而是隻需要儲存位於目標SQL中的跟目標表相關的查詢列和連線列就足夠了;我們把S所對應的每一個Hash Partition記為Si;

5、  在構建Si的同時,Oracle會構建一個位圖(BITMAP),這個點陣圖用來標記Si所包含的每一個Hash Bucket是否有記錄(即記錄數是否大於0)

6、  如果S的資料量很大,那麼在構建S所對應的Hash Table時,就可能會出現PGA的工作區(WORK AREA)被填滿的情況,這時候Oracle會把工作區中現有的Hash Partition中包含記錄數最多的Hash Partition寫到磁碟上(TEMP表空間);接著Oracle會繼續構建S所對應的Hash Table,在繼續構建的過程中,如果工作區又滿了,則Oracle會繼續重複上述挑選包含記錄數最多的Hash Partition並寫回到磁碟上的動作;如果要構建的記錄所對應的Hash Partition已經事先被Oracle寫回到了磁碟上,則此時Oracle就會去磁碟上更新該Hash Partition,即會把該條記錄和hash_value_2直接加到這個已經位於磁碟上的Hash Partition的相應Hash Bucket中;注意,極端情況下可能會出現只有某個Hash Partition的部分記錄還在記憶體中,該Hash Partition的剩餘部分和餘下的所有Hash Partition都已經被寫回到磁碟上

7、  上述構建S所對應的Hash Table的過程會一直持續下去,直到遍歷完S中的所有記錄為止;

8、  接著,Oracle會對所有的Si按照它們所包含的記錄數來排序,然後Oracle會把這些已經排好序的Hash Partition按順序依次、並且儘可能的全部放到記憶體中(PGA的工作區),當然,如果實在放不下的話,放不下的那部分Hash Partition還是會位於磁碟上。我認為這個按照Si的記錄數來排序的動作不是必須要做的,因為這個排序動作的根本目的就是為了儘可能多的把那些記錄數較小的Hash Partition保留在記憶體中,而將那些已經被寫回到磁碟上、記錄數較大且現有記憶體已經放不下的Hash Partition保留在磁碟上,顯然,如果所有的Si本來就都在記憶體中,也沒發生過將Si寫回到磁碟的操作,那這裡根本就不需要排序了

9、     至此Oracle已經處理完S,現在可以來開始處理B了;

10、 Oracle會遍歷B,讀取B中的每一條記錄,並對B中的每一條記錄按照該記錄在表T2中的連線列做雜湊運算,這個雜湊運算和步驟3中的雜湊運算是一模一樣的,即這個雜湊運算還是會用步驟3中的hash_func_1和hash_func_2,並且也會計算出兩個雜湊值hash_value_1和hash_value_2;接著Oracle會按照該記錄所對應的雜湊值hash_value_1去Si裡找匹配的Hash Bucket;如果能找到匹配的Hash Bucket,則Oracle還會遍歷該Hash Bucket中的每一條記錄,並會校驗儲存於該Hash Bucket中的每一條記錄的連線列,看是否是真的匹配(即這裡要校驗S和B中的匹配記錄所對應的連線列是否真的相等,因為對於Hash運算而言,不同的值經過雜湊運算後的結果可能是一樣的),如果是真的匹配,則上述hash_value_1所對應B中的記錄的位於目標SQL中的查詢列和該Hash Bucket中的匹配記錄便會組合起來,一起作為滿足目標SQL連線條件的記錄返回;如果找不到匹配的Hash Bucket,則Oracle就會去訪問步驟5中構建的點陣圖,如果點陣圖顯示該Hash Bucket在Si中對應的記錄數大於0,則說明該Hash Bucket雖然不在記憶體中,但它已經被寫回到了磁碟上,則此時Oracle就會按照上述hash_value_1的值把相應B中的對應記錄也以Hash Partition的方式寫回到磁碟上,同時和該記錄儲存在一起的還有該記錄用hash_func_2計算出來的hash_value_2的值;如果點陣圖顯示該Hash Bucket在Si中對應的記錄數等於0,則Oracle就不用把上述hash_value_1所對應B中的記錄寫回到磁碟上了,因為這條記錄必然不滿足目標SQL的連線條件;這個根據點陣圖來決定是否將上述hash_value_1所對應B中的記錄寫回到磁碟的動作就是所謂的“點陣圖過濾”我們把B所對應的每一個Hash Partition記為Bj

11、 上述去Si中查詢匹配Hash Bucket和構建Bj的過程會一直持續下去,直到遍歷完B中的所有記錄為止;

12、 至此Oracle已經處理完所有位於記憶體中的Si和對應的Bj,現在只剩下位於磁碟上的Si和Bj還未處理;

13、 因為在構建Si和Bj時用的是同樣的雜湊函式hash_func_1和hash_func_2,所以Oracle在處理位於磁碟上的Si和Bj的時候可以放心的配對處理,即只有對應Hash Partition Number值相同的Si和Bj才可能會產生滿足連線條件的記錄;這裡我們用Sn和Bn來表示位於磁碟上且對應Hash Partition Number值相同的Si和Bj

14、 對於每一對兒Sn和Bn,它們之中記錄數較少的會被當作驅動結果集,然後Oracle會用這個驅動結果集的Hash Bucket裡記錄的hash_value_2來構建新的Hash Table,另外一個記錄數較大的會被當作被驅動結果集,然後Oracle會用這個被驅動結果集的Hash Bucket裡記錄的hash_value_2去上述構建的新Hash Table中找匹配記錄;注意,對每一對兒Sn和Bn而言,Oracle始終會選擇它們中記錄數較少的來作為驅動結果集,所以每一對兒Sn和Bn的驅動結果集都可能會發生變化,這就是所謂的“動態角色互換”

15、 步驟14中如果存在匹配記錄,則該匹配記錄也會作為滿足目標SQL連線條件的記錄返回;

16、 上述處理Sn和Bn的過程會一直持續下去,直到遍歷完所有的Sn和Bn為止。

對於雜湊連線的優缺點及適用場景,我們有如下總結:

Ÿ     雜湊連線不一定會排序,或者說大多數情況下都不需要排序;

Ÿ     雜湊連線的驅動表所對應的連線列的可選擇性應儘可能的好,因為這個可選擇性會影響對應Hash Bucket中的記錄數,而Hash Bucket中的記錄數又會直接影響從該Hash Bucket中查詢匹配記錄的效率;如果一個Hash Bucket裡所包含的記錄數過多,則可能會嚴重降低所對應雜湊連線的執行效率,此時典型的表現就是該雜湊連線執行了很長時間都沒有結束,資料庫所在database server上的CPU佔用率很高,但目標SQL所消耗的邏輯讀卻很低,因為此時大部分時間都耗費在了遍歷上述Hash Bucket裡的所有記錄上,而遍歷Hash Bucket裡記錄這個動作是發生在PGA的工作區裡,所以不耗費邏輯讀

Ÿ     雜湊連線只適用於CBO、它也只能用於等值連線條件(即使是雜湊反連線,Oracle實際上也是將其轉換成了等價的等值連線)

Ÿ     雜湊連線很適合於一個小表和大表之間的表連線,特別是在小表的連線列的可選擇性非常好的情況下,這時候雜湊連線的執行時間就可以近似看作是和全表掃描那個大表所耗費的時間相當

Ÿ     當兩個表做雜湊連線時,如果這兩個表在施加了目標SQL中指定的謂詞條件(如果有的話)後得到的結果集中資料量較小的那個結果集所對應的Hash Table能夠完全被容納在記憶體中時(PGA的工作區),則此時的雜湊連線的執行效率會非常高。

來源: http://www.dbsnake.com/oracle-hash-join.html