表連線cost

darrenduan發表於2016-07-06

hash join必須使用cbo(因此必須表分析)

NLJ:
根據連線鍵,把小表的每一行,和大筆的每一行做對比。 一般情況下會對大表連線鍵上建index。
成本計算:讀小表的行+(小表的每一行×讀取大表的行)
SMJ:
讀取小表和大表讀的行,根據連線鍵排序,然後根據排序後的資料集(小表的和大表的)合進行連線。
理想狀態:2個表的排序操作都能在記憶體hash進行
常規情況:2階段進行:
          1.sort run階段:資料讀取到記憶體,排序,寫出到臨時表空間。直到所有的row sourse完成排序。
          2.merge階段:之前每次寫到臨時表空間的資料(即sort run)被重新讀入到記憶體,進行merge。
成本計算:讀取小表的行+寫小表的run sort到temp表空間+
          讀取大表的行+寫大表的run sort到temp表空間+
          cpu對小表和大表的排序消耗
join連線中的並行機制:
能在NLJ和SMJ中使用。併發查詢的執行計劃是一個樹形結構(DFO),每個樹上的DFO節點是一個sql操作過程,並且能把該操作過程能指派到一個query slave程式中。
Hash Join:
用在條件為等號的環境下,hash連線的效率要比SMJ和NLJ要高(如果索引的blevel比較高),且hash join不要求一定要有索引。
hash join的基本演算法是在記憶體中建立hash table,小表叫做build input,理想狀態下,build input在記憶體中;大表叫做probe input。
在實際的情況下,build input不一定能完全放在記憶體中,此時,和probe input一樣,build input的溢位部分,會在磁碟上用hash函式分割成小的不連續的分割槽。
hash連線分2個階段進行:
1.partitioning階段:即在記憶體中存放build input,若放不下,則和probe input一樣,在磁碟上利用hash函式將input分割成小的不連續的分割槽。
1.join階段:在相同的鍵值上,將build input和probe input的分割槽進行一一配對,並且join。
以上的hash連線的演算法也叫grace join。
 
hash演算法的限制:該演算法是假設hash之後連線值傾斜度(skew)不高,使得每個partition上保持大約相同數量的rows。但是事實上不可能保證每個partition有大約相同數量的rows。
 
hybrid hash join是在oracle 7.3之後應用的比較高效的hash演算法,它是在grace join的基礎上,儘量在記憶體在搭建build input。
但是由於不可能在每個partition中保證相同的rows,後來有出來一些技術如bit-vector filtering、role reversal和histograms。我們將在後面的章節講到這些技術。
分割槽的數量,我們叫做fan-out。fan-out太多會導致partition較多,從而影響IO,但是如果fan-out太少又會造成數量較少的大partition,這些大partition無法放在hash記憶體中。因此選擇一個合適的fan-out和partition 大小,是hash join調優的關鍵。
 
當partitioning之後,build input或者probe input如果在記憶體中無法放下,hash table的溢位部分將會做nested-loops hash join。
hash table是由部分(在記憶體內的)build input partition和所有的probe input連線構成。剩餘的不在記憶體中的build input將透過迭代的方式繼續獲取,直到所有的build input被迭代完。
 
hash join 規則:
假設有2個表:
S = { 1, 1, 1, 3, 3, 4, 4, 4, 4, 5, 8, 8, 8, 8, 10 }
B = { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 8, 9, 9, 9, 10, 10, 11}
首先會根據hash_area_size確定小表是否能做build table。如果build input不能完全的在記憶體中,那麼build input就被會分割槽,分割槽的數量我們稱為fan-out,
fan-out是由hash_area_size和cluster size決定的,cluster size是指分割槽中還沒被寫出到臨時表空間的連續塊的數量。
cluster size=db_block_size * hash_multiblock_io_count,hash_multiblock_io_count在oracle9i中是隱含引數
hash演算法會把S和B表的連線列分成不連線的桶(bucket),桶也叫做分割槽(partition),hash演算法是儘量的減少資料的傾斜度,使得資料儘量均勻的分佈。
 
以上面的S表和B表為例,如果我們簡單的假設hash演算法是取餘,則:
S的分割槽為:{0,1,3,4,5,8}
B的分割槽為:{0,1,2,3,8,9}
經過這樣的分割槽之後,只需要相應的分割槽之間做join即可(也就是所謂的partition pairs),如果有一個分割槽為NULL的話,則相應的分割槽join即可忽略。即他們可以在0,1,3,8上做連線。
相應的如果我們用SMJ或者NLJ,在連線上的消耗要高很多。
 
當build input被讀入hash area記憶體準備進行分割槽的時候,build input表中的唯一列值被作為連線鍵構建起來,即所謂的點陣圖向量(bitmap vector)。
按照上面的例子,bitmap vector為:{1,3,4,8,10}。
bitmap vector用來決定在partitioning 階段和大表(probe input)進行連線的時候,哪些行是需要的,哪些是不需要的,不需要的將被丟棄,這就是我們上面說的bit-vector filtering技術。
當對B表進行分割槽時,將每一個連線鍵上的值與點陣圖向量相比較,如果不在其中,則將其記錄丟棄。在我們這個例子中,B表中以下資料將被丟棄
這個例子中,B表中以下資料將被丟棄{0,0,2,2,2,2,2,2,9,9,9,9,9}。
 
當第一個S分割槽和B分割槽做完連線後,需要將第i個S分割槽和B分割槽讀入記憶體中做連線,此時會根據分割槽的大小,自動的選擇哪個做build input,哪個做probe input。這就是動態角色轉換技術,即我們前面所說的role reversal。
總體說來,hash演算法為如下步驟(以下考慮的是hash area size不夠大,需要寫出到磁碟的情況):
1.決定fanout的數量,即分割槽的數量。分割槽數量×cluster大小
<=記憶體中能用的hash area比例×hash area size大小
2.讀取S表,根據內部的hash演算法(我們暫時稱作hash_fun_1),將連線列上的值map到分割槽。在此步驟,還利用另一個hash函式(我們稱作hash_fun_2)產生另一個hash值,和連線鍵一起存放。該值將在後續的構建hash table中用到。
3.為S表的獨立的連線鍵形成的bitmap向量
4.根據partition的大小排序,使得儘量多(也就是儘量小的partition進入記憶體。這也就是之前要根據partition大小排序的原因)的分割槽放入記憶體來構建hash table。如果記憶體不夠放下所有的parittion,則輸出到temp segment上)。
5.利用之前的hash值,構建S表的hash table
6.讀取B表,根據點陣圖向量過濾,如果透過hash演算法後B的值與點陣圖向量比較不在其中,則丟棄該行。
7.將過濾後B表的行,利用內部的hash_fun_1和連線鍵,形成partition
8.如果B表的行能在記憶體中形成分割槽,就利用內部的hash_fun_2執行連線,並且形成合適的hash桶。
9.如果不能在記憶體中形成分割槽,則將S的分割槽、連線鍵、B表的剩餘行寫出到磁碟。
10.從磁碟中讀取未處理的S表和B表的分割槽。利用內部hash_fun_2值,構建hash table,在構建時將使用動態角色轉換技術。在第一次迴圈中,最佳化器將先使用小表做build input,大表做probe input,角色轉換技術僅在第一次迴圈後使用。
11.如果probe input或者build input中(已經經過角色轉換了)較小的那個還是不能放入到記憶體中,則將讀取較小的那個build input到記憶體chunk中,並且迴圈的和probe inputhash 連線。這我們叫做nested hash loops join
hash join的成本計算:
1.最簡單的情況,hash area足夠大能放下S表分割槽後的所有的build input:
cost(HJ)=read(S)+build hash table in memory(cpu)+read(B)+perform in memory join(cpu)
如果忽略cpu的成本,cost(HJ)無限接近於 read(s)+read(b)
 
2.當hash area(後面用M表示)不夠大,不能容納build input,S,它將會寫出到磁碟。當然了,表B也會寫出到磁碟:
total cost 無限接近於 cost(HJ迴圈1)+cost(HJ迴圈2)
其中cost(HJ迴圈1) 無限接近於 read(S)+read(B)+write((S-M)+(B-B*M/S))
即上述2至9步。
 
由於HJ迴圈2使用了nested hash loops join,hash join的演算法處理Si和Bi分割槽。當每個build input的chunk被讀取時,probe input將被多次讀。
因此cost(HJ迴圈2) 無限接近於 read((S-M)+n×(B-B*M/S))
即上述10至11步。
n為進行nested hash loops join的次數,n一般在10以上,也就是需要構建的partition大於10倍的hash area。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26526320/viewspace-2121638/,如需轉載,請註明出處,否則將追究法律責任。

相關文章