漫談資料庫中的join

2021-10-02 15:40:16 字數 4291 閱讀 7308

join是我們這些整天與資料打交道的人繞不開的乙個詞,不管是在傳統的關係型資料庫,還是在大資料領域的資料倉儲/資料湖中,join都是常客。特別是對於olap業務而言,幾乎每個查詢都需要用join來建立表之間的關係,地位非常之重要。本文就來簡單講解一些主要的join演算法(真的非常簡單哦)。

nested-loop (nl) join是所有join演算法中最*****的一種。假設有兩張表r和s,nl join會用二重迴圈的方法掃瞄每個(r, s)對,如果行r和行s滿足join的條件,就輸出之。顯然,其i/o複雜度為o(|r||s|)。

隨著參與join的表個數增加,迴圈巢狀的層數就越多,時間複雜度也越高。因此雖然它的實現很簡單,但效率也較低,所以在它的基礎上衍生出了改進方案:block nested-loop (bnl) join。

bnl join的思路是:對於外層迴圈的表r,不再逐行掃瞄,而是一次載入一批(即所謂block)資料進入記憶體,並且將它們按join key雜湊在雜湊表裡,然後也按批掃瞄表s,並與雜湊表進行比對,能夠對的上的行就作為join的輸出。乙個block資料的大小一般是乙個或多個記憶體頁的容量。這樣就可以將i/o複雜度從o(|r||s|)降低到o[p(r) * p(s) / m],其中p(r)、p(s)分別代表r和s換算成頁數的大小,m代表可用記憶體中的總頁數。

下圖示出bnl join的過程,以及nl/bnl join的偽碼描述。

在mysql 5.5版本之前,其join演算法只有原生的nl join,5.5版本之後則引入了bnl join進行優化,具體可以參考其官方文件中對應的nested-loop join algorithms章節。mysql本身不是為了olap業務設計的,因此bnl join已經能夠滿足非大量資料的join需求。

以數倉維度建模的思想考慮,olap業務大多是維度表和事實表的join。由於維度表一般較小,如果它可以整體放進記憶體,那麼就可以通過兩步完成join。設r為維度表(小表),s為事實表(大表):

將r的所有資料按key雜湊,構成雜湊表,value就是原來的行資料;

掃瞄s,計算key的雜湊值並觀察是否在雜湊表內,輸出結果。

其過程如下圖所示。

其中,第1步名為build階段(建立雜湊表),第2步名為probe階段(探測雜湊表)。相應地,小表r就稱為build table,大表s就稱為probe table。hash join的i/o複雜度是o(|r| + |s|),效率很高,但是它受到兩點限制:

小表必須保證能完全放入記憶體;

只適用於equi join,即僅包含"="條件的連線。

如果小表不能完全放入記憶體,就只能分批載入,實質上就退化成了bnl join演算法。為了避免這種退化,也有乙個優化方案,即grace hash join演算法。它的思想也很直接:將r、s兩張表都以join key雜湊到分割槽,然後對於劃分到同乙個分割槽的r、s分片資料分別再進行原生hash join的build與probe過程(注意,分割槽與join兩個階段所用的雜湊函式是不同的),將所有分片合併起來就是最終的join結果。

由此可見,grace hash join消滅了對s表的重複掃瞄,i/o複雜度為o[p(r) + p(s)],理論效率比bnl join高很多了。

下圖示出grace hash join的過程。其名稱中的grace並非人名,而是首個採用這種演算法的資料庫系統的名字。

mysql中目前還沒有hash join的實現,但在oracle的7.3版本之後引入了hash join演算法,專門用來優化大小表join的情況。在大資料領域中hash join是絕對的標配,spark sql就充分利用了它,並且又分為兩種情況:

關於spark sql中join的實現方法,前人已經寫過非常好的分析文章,參見hbasefly的部落格。作為spark重度使用者,本來是想隨著介紹join演算法一起說兩句的,想想還是不做重複工作了,畢竟時間寶貴得很。

從前面的介紹,看官應該可以知曉一點:nl/bnl join適用於小表與小表的連線,(grace) hash join適用於小表與大表的連線。那麼兩張大表的連線用什麼方法比較好呢?答案就是sort-merge join。

實際上兩張大表join完全可以用grace hash join來做,但是sort-merge join提供了另一種思路:它首先根據r和s的join key分別對兩張表進行排序,然後同時遍歷排序後的r和s,如果遇到了相同的key就輸出,否則繼續取下標較小的一方的資料。英文維基上提供了乙份很好的偽碼,如下:

function sortmerge(relation left, relation right, attribute a)

var relation output

var list left_sorted := sort(left, a) // relation left sorted on attribute a

var list right_sorted := sort(right, a)

var attribute left_key, right_key

var set left_subset, right_subset // these sets discarded except where join predicate is satisfied

advance(left_subset, left_sorted, left_key, a)

advance(right_subset, right_sorted, right_key, a)

while not empty(left_subset) and not empty(right_subset)

if left_key = right_key // join predicate satisfied

add cartesian product of left_subset and right_subset to output

advance(left_subset, left_sorted, left_key, a)

advance(right_subset, right_sorted, right_key, a)

else if left_key < right_key

advance(left_subset, left_sorted, left_key, a)

else // left_key > right_key

advance(right_subset, right_sorted, right_key, a)

return output

// remove tuples from sorted to subset until the sorted[1].a value changes

function advance(subset out, sorted inout, key out, a in)

key := sorted[1].a

subset := emptyset

while not empty(sorted) and sorted[1].a = key

insert sorted[1] into subset

remove sorted[1]

可見,sort-merge join的時間主要消耗在了排序上,其i/o複雜度可以表示為o[p(r) + p(s) + p(r) · logp(r) + p(s) · logp(s)],或者漸近地簡化為o[p(r) · logp(r) + p(s) · logp(s)]。它的複雜度比grace hash join還要高,但是當join key已經有序或者基本有序時,它的威力就顯現出來了。例如,如果在rdbms中以兩張表的主鍵作為join key,主鍵索引實際上就是有序的。

再以spark sql為例,由於在sort-merge join時會先進行shuffle,而當前spark採用的是sort-based shuffle演算法(關於其細節,可以參考我之前寫的文章),所以實際上變相地省去了sort步驟,只剩下merge了。spark sql中的sort-merge join實現如下圖所示。

仍然出自

grace hash join與sort-merge join的相同點在於都採用了分治(divide-and-conquer)的思想。前者是將資料雜湊成分片,然後分別處理;後者的歸併排序演算法天然地就是分治法的實現,不管內部歸併還是外部歸併都是如此。

晚安晚安。

資料庫中 join 的使用

關聯多張表資料,並通過條件篩選符合條件的資料。一般來說,會有四種 1.left join 左連線 2.right join 右連線 3.inner join 內連線 4.full join 全連線 left join以左表作為基準,然後加入符合條件的右表資料,最終得到的資料數目基本會不小於左表的資料...

漫談資料庫

談到資料庫這個詞,我想無論是菜鳥還是高手都不會感到陌生。隨著資訊化時代的到來,資料庫已被廣泛運用於各類電腦網路和管理系統中。如果沒有資料庫的話,今天的網際網路就不會這樣 絢麗多姿 我們所看到的企業管理系統將只能成為一種電腦的裝飾品。我有不少朋友,雖然他們對資料庫各有自己的看法,但在有一點上是達成共識...

漫談資料庫鎖

函式獲取鎖導致的一些問題,主要有兩類問題 1 乙個連線中不能同時獲取兩把鎖,因為獲取後乙個的時候會自動釋放前面一把鎖 另外如果獲得鎖或者釋放鎖所使用的connection不一樣,假如獲得鎖的connection被連線池 了,也可能會導致第一把鎖自動釋放,最終導致你的業務還沒有處理完,別人也同時處理相...