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

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