歸併連接是唯一的生成排序結果的連接方式。其可以劃分爲兩步:
- 排序操作:對兩個輸入結果集按照連接字段排序;
- 歸併操作:歸併經過排序的結果集到一起。
排序
之前我們已經介紹過歸併排序,在這歸併排序是一個好的算法但不是最好的,如果內存足夠會有更好的算法。
有些場景下數據集已經經過了排序,例如:
- 如果表示自然排序的,比如一張按照索引組織數據的表;
- 如果連接條件中的關係是一個索引;
- 如果連接一個查詢過程中已經排序過的中間結果。
歸併連接
這一步和我們之前介紹的歸併排序很相似。但是這次我們不用取出兩個關係的所有元素,只需取出相等的元素即可,下邊是其過程:
- 1) 比較兩個關係中的當前元素(第一次比較就是其第一個元素);
- 2) 如果兩個元素相等則將其放入結果集中,然後比較下一個元素;
- 3) 如果不相等則將本次比較元素較小關係的指針移向下一個元素,另外一個關係指針不動。
- 4) 重複1、2、3步,直到其中一個關係的指針到達最後一個元素。
因爲兩個關係時排過序的,所以無須將指針向前移動。這是一個簡化的算法,因爲它並沒有考慮相同元素出現多次的情況,否則它會複雜很多。
如果兩個關係已經排序,則歸併連接時間複雜度是O(N+M);否則是O(N*Log(N) + M*Log(M))。
下邊是一個考慮了相同元素出現多次情況的僞代碼(但我並不保證其實100%正確哦):
mergeJoin(relation a, relation b)
relation output
integer a_key:=0;
integer b_key:=0;
while (a[a_key]!=null or b[b_key]!=null)
if (a[a_key] < b[b_key])
a_key++;
else if (a[a_key] > b[b_key])
b_key++;
else //Join predicate satisfied
//i.e. a[a_key] == b[b_key]
//count the number of duplicates in relation a
integer nb_dup_in_a = 1:
while (a[a_key]==a[a_key+nb_dup_in_a])
nb_dup_in_a++;
//count the number of duplicates in relation b
integer dup_in_b = 1:
while (b[b_key]==b[b_key+nb_dup_in_b])
nb_dup_in_b++;
//write the duplicates in output
for (int i = 0 ; i< nb_dup_in_a ; i++)
for (int j = 0 ; i< nb_dup_in_b ; i++)
write_result_in_output(a[a_key+i],b[b_key+j])
a_key=a_key + nb_dup_in_a-1;
b_key=b_key + nb_dup_in_b-1;
end if
end while
哪種join是最好的呢
好了,三種常見join方式我們都介紹完了,那麼哪種join是最好的呢?如果有最好的,那就沒必要存在3種join了。這個問題很難回答,因爲影響它的因素很多:
- 空閒內存的大小:沒有足夠的內存,那就沒法使用強大的Hash Join;
- 兩個數據集合的大小:如果是一個大表和一個小表,那麼nested loop join會比hash join更快,因爲構建hash表比較耗時;如果是兩個大表,則loop join會很耗CPU;
- 索引:對於兩個B+Tree索引,最好使用merge join;
- 如果結果集需要排序:即時是兩個沒有排序的數據集,爲了最終排序的結果也要使用merge join;
- 如果關係已經排序:最佳選擇是merge join;
- 連接類型:是相等條件(如: tableA.col1 = tableB.col2)?是內連接、外連接、全連接,還是自連接?有些連接在特定場景下效果不錯;
- 數據的分佈情況:如果數據分佈比較集中,比如使用人的last name來連接(很多人的last name都是相同的),這時使用hash join很糟糕,因爲hash衝突會很嚴重;
- 是否需要多進程或多線程執行連接操作。
如果想了解更多,請查閱DB2、Orcale和SQL Server官方文檔。