Join 是 SQL 中的經常使用操做。在實際的數據庫應用中,咱們常常須要從多個數據表中讀取數據,這時咱們就可使用 SQL 語句中的鏈接(join),在兩個或多個數據表中查詢數據。git
經常使用 Join 算法
經常使用的多表鏈接算法主要有三類,分別是 Nested-Loop Join、Hash Join 和 Sort Merge Join。算法
Nested-Loop Join
Simple Nested-Loop Join 是最簡單粗暴的 Join 算法 ,即經過雙層循環比較數據來得到結果,可是這種算法顯然太過於粗魯,若是每一個表有 1 萬條數據,那麼對數據比較的次數=1萬 * 1萬 =1億次,很顯然這種查詢效率會很是慢。數據庫
在 Simple Nested-Loop Join 算法的基礎上,延申出了 Index Nested-Loop Join 和 block Nested-Loop Join。前者經過減小內層表數據的匹配次數優化查詢效率;後者則是經過一次性緩存外層表的多條數據,以此來減小內層表的掃表次數,從而達到提高性能的目的。 緩存
Batched Key Access Join (BKA Join) 能夠看做是一個性能優化版的 Index Nested-Loop Join。之因此稱爲 Batched,是由於它的實現使用了存儲引擎提供的 MRR(Multi-Range Read) 接口批量進行索引查詢,並經過 PK 排序的方法,將隨機索引回錶轉化爲順序回表,必定程度上加速了查索引的磁盤 IO。性能優化
Hash Join
兩個表如果元組數目過多,逐個遍歷開銷就很大,Hash Join(哈希鏈接)是一種提升鏈接效率的方法。哈希鏈接主要分爲兩個階段:創建階段(build phase)和探測階段(probe phase)。架構
在創建階段,首先選擇一個表(通常狀況下是較小的那個表,以減小創建哈希表的時間和空間),對其中每一個元組上的鏈接屬性(join attribute)採用哈希函數獲得哈希值,從而創建一個哈希表。分佈式
在探測階段,對另外一個表,掃描它的每一行並計算鏈接屬性的哈希值,與 bulid phase 創建的哈希表對比,如有落在同一個 bucket 的,若是知足鏈接謂詞(predicate)則鏈接成新的表。函數
在內存足夠大的狀況下,創建哈希表的整個過程都在內存中完成,完成鏈接操做後才放到磁盤裏。所以這個過程也會帶來不少的內存消耗。oop
Merge Join
Merge join 第一個步驟是確保兩個關聯表都是按照關聯的字段進行排序。若是關聯字段有可用的索引,而且排序一致,則能夠直接進行 merge join 操做;不然須要先對關聯的表按照關聯字段進行一次排序(就是說在 merge join 前的兩個輸入上,可能都須要執行一個排序操做,再進行 merge join)。 性能
兩個表都按照關聯字段排序好以後,merge join 操做從每一個表取一條記錄開始匹配,若是符合關聯條件,則放入結果集中;不然,將關聯字段值較小的記錄拋棄,從這條記錄對應的表中取下一條記錄繼續進行匹配,直到整個循環結束。
Merge join 操做自己是很是快的,可是 merge join 前進行的排序可能會帶來較大的性能損耗。
ZNBase 採用的分佈式 join 算子
ZNBase 是由浪潮開源的一款分佈式 NewSQL 數據庫,其採用的 Join 算法包括 Merge join、Hash join 和 Lookup join 。
Merge join
在兩個表索引排序相同的狀況下,Merge joins 比 Hash joins 在計算和內存方面更高效,性能更好。Merge joins 要求在相等列上索引兩個表,而且索引必須具備相同的順序。若是不知足這些條件,ZNBase 纔會轉向較慢的 Hash joins。
Merge joins 在兩個表的索引列上執行,以下所示:
- ZNBase 檢查相等列上的索引,而且它們的排序方式相同(即 ASC 或 DESC)。
- ZNBase 從每一個表中取一行並進行比較。
-
- 對於內鏈接:
- 若是行相等,則 ZNBase 返回行。
- 若是有多個匹配項,則返回匹配項的笛卡爾積。
- 若是行不相等,ZNBase 將丟棄較低值的行並使用下一行重複該過程,直處處理完全部行。
- 對於外鏈接:
- 若是行相等,則 ZNBase 返回行。
- 若是有多個匹配項,則返回匹配項的笛卡爾積。
- 若是行不相等,則 ZNBase 將返回 NULL 非匹配列,並使用下一行重複該過程,直處處理完全部行。
- 對於內鏈接:
HashJoin
若是沒法使用一個 Merge join,ZNBase 將使用一個 Hash join。Hash joins 的計算量很大,須要額外的內存。
Hash joins 在兩個表上執行,以下所示:
- ZNBase 讀取兩個表並嘗試選擇較小的表。
- ZNBase 在較小的表上建立內存中的哈希表。若是哈希表太大,它將溢出到磁盤存儲(這可能會影響性能)。
- 而後,ZNBase 掃描大表,查找哈希表中的每一行。
Lookup Join
對於普通的 join 算法,咱們注意到,沒有必要對於 Outer 表中每行數據,都對 Inner 表進行一次全表掃操做,不少時候能夠經過索引減小數據讀取的代價,這就用到了 Lookup join。
Lookup join 的適配前提是,在 join 的兩個表中,Outer 表上的對應索引列存在索引。在執行過程當中,首先讀取小表的數據,而後去大表的索引中找到大概的 scan 範圍,拿大表的數據與小表的數據比較,推動大表最後就能夠得出結果。其執行過程簡述以下:
- 從 Inner 表中取一批數據;
- 經過 join key 以及這一批數據構造在 outer 表的取值範圍,只讀取對應範圍內的數據
- 對從 inner 表取出的每一行數據,都與 2 中取出的對應範圍內的每一條數據執行 join 操做並輸出結果交給上層處理
- 重複步驟 1.2.3 直到遍歷完 Outer 表爲止。
Lookup Join 在執行時會不斷變動狀態,在不一樣階段進入不一樣的狀態作 join 處理:
階段一: jrReadingInput 階段
這個階段讀取小表的一塊塊數據,並對每一行數據開始構建對於大表的 index scan 的範圍(命名爲 span),構建完成後進入下一個階段。當小表的這一塊數據被讀完後會回到這個狀態繼續讀取,重複直到小表被讀完。
階段二: jrPerformingLookup 階段
這個階段經過階段一獲得的 span,將這個 span 中的數據取出放在一個容器中,讓小表讀出的一塊數據每一行去這個容器中的每一行數據作 lookup 查找,執行 join 操做並將結果存儲在容器中。當數據匹配完成後進入下一階段。
階段三: jrEmittingRows 階段
從階段二中的容器中取出 join 結果輸出到上層。
分佈式 join 計算和數據重分佈
與傳統數據庫相比,分佈式數據庫的架構有很大的不一樣。以 ZNBase 爲例,數據庫架構能夠分爲 SQL 層和存儲層,SQL 層的計算節點須要計算數據所在的分片,而後從多個存儲節點拉取所需的數據。
目前 ZNBase 採用兩種辦法實現分佈式計算時表的關聯:
重分佈
將兩表按 join 的列,按 hash 特徵從新分佈到每一個節點上。執行分佈式的 join 時,若是各個執行節點的數據沒有按照 join 列的特徵進行分佈,這個時候就會將數據進行 hash 重分佈,具體操做以下:
1)選取一個 hash 函數對該行數據進行 join 列的 hash 值計算
2)對參與計算的節點數取餘
根據取餘結果將特定行數據分發至對應計算節點進行 join 計算。
廣播
將數據量較小的表進行廣播。
相關的代價計算爲:
M + N > min(M,N) * L:廣播;
M + N <= min(M,N) * L:重分佈。
M 和 N 分別爲左右表的行數,L 爲參與計算的節點個數。
總結
本文介紹了經常使用的多表鏈接 Join 算法,以及分佈式數據庫 ZNBase 採用的 Join 算法和分佈式 Join 策略。對相關技術或產品有任何問題歡迎提 issue 或在社區中留言討論。同時歡迎廣大對分佈式數據庫感興趣的開發者共同參與 ZNBase 項目的建設。
關於 ZNBase 的更多詳情能夠查看:
ZNBase 官網:http://www.znbase.com/
聯繫郵箱:haojingyi@inspur.com