天天看點

程式員必備的資料庫知識 2:Join 算法

作者:NineData

前言

連接配接(Join)是關系資料庫重要特性,它和事務常被作為資料庫與檔案系統的兩個重要差別項。程式員江湖一直流傳着某某 baba 的神秘開發寶典,其中資料庫部分有重要一條避免過多表的 Join,奈何 Join 特性實在是好用,廣大程式員們無視着寶典的諄諄教誨,依舊每天樂此不疲的使用這 Join 特性。那資料庫有哪些連接配接算法呢?它們的實作方式是怎樣呢?它們之間又有什麼差別呢?為什麼需要這麼多不同的連接配接算法呢?如果你也好奇這些問題,那麼請繼續往下閱讀,本文将逐一回答上述問題。

關聯算法簡介

關系型資料庫主要有三種 Join 算法:Nested Loop Join,Hash Join、 Merge Join,像 Oracle、SqlServer 、DB2 這幾位資料庫中的老炮均支援三種 Join 方式;MySQL 長久以來隻支援 NLJ 或其變種,直到8.0.18 版本後才有限的支援 Hash Join。在 「程式員必備的資料庫知識:資料存儲結構」一文中介紹了資料庫幾種常見的資料存儲結構,存儲引擎之上是計算引擎。以 MySQL 資料庫為例,計算引擎層通常包括 SQL 接口、解析器、查詢優化器、緩存等元件,資料庫 Join 實作就在計算引擎的查詢優化器中。

程式員必備的資料庫知識 2:Join 算法

以 MySQL 資料庫為例,計算引擎層

然而資料庫具體選擇哪種連接配接算法,是由本身決定的,主要根據目前的優化器模式、表大小、連接配接列是否有索引和排序等因素決定。

多表連接配接方式又分為:内連接配接(等值連接配接)、外連接配接和交叉連接配接,外連接配接又分為:左外連接配接、右外連接配接和全外連接配接。對于不同方式的連接配接查詢,使用相同的 Join 算法也會有不同的成本産生,這和實作方式緊密相關的。本文不涉及同一個 Join 算法在不同連接配接方式的情況。

Nested Loop Join

NLJ 是 MySQL 最重要的連接配接方式,也是 MySQL 長期唯一支援的連接配接方式,直到 8.0.18 版本 MySQL 才有限的支援 Hash Join。那什麼是 NLJ 呢?從概念上講,NLJ 相當于兩個嵌套循環,用第一張表做 Outter Loop,第二張表做 Inner Loop,Outter Loop 的每一條記錄跟 Inner Loop 的記錄作比較,最終符合條件的就将該資料記錄。

可以用以下僞代碼表示:

程式員必備的資料庫知識 2:Join 算法

NLJ 可以用僞代碼表示

如果忽略記憶體和 CPU 的時間,它的成本是:

Cost(NLJ) = Read(M) + M * Read(N) (其中M和N表示需要讀兩個關聯表中的資料行數)

NLJ 的算法比較簡單,并且對 Join 的連接配接條件沒有特殊要求(Hash Join 通常隻支援等值,Merge Join 一般不支援不等和like),在有索引過濾性較好的 OLTP 場景下,它的查詢效率很高。缺點也同樣明顯,由于它的成本是:Read(M) + M * Read(N) 。在 OLAP 需要大表間 Join 場景下,它的查詢效率變得比較差。在 MySQL 中 NLJ 還有兩個變種:Index Nested Loop Join(INLJ)、Block Nested Loop Join(BNLJ),本文不涉及這方面的擴充,有興趣的同學可以深入研究。

Hash Join

Hash Join 是Oracle、SQLServer 、PostgreSQL 中重要的關聯算法,當兩個表關聯時,選擇一張表按照 join 條件給的列建構 hash 表,然後将第二張表的每行記錄去探測 hash 表中的資料,如果符合連接配接條件就輸出該資料。前一張表我們叫做 build 表,後一張表我們的叫做 probe 表。為了減少記憶體使用量,通常選擇小表作為 hash 表,大表作為 probe 表。

程式員必備的資料庫知識 2:Join 算法

Hash Join

經典 Hash Join 主要有兩個步驟:選擇 hash 表,掃描該表并建立 hash 表;将另一個作為 probe 表,掃描每一行資料,然後在 hash 表中找尋對應的滿足條件的記錄。忽略記憶體和 CPU 時間,它的成本是:

Cost(HJ) = Read(M) + Read(N)

Hash Join 需要把表放到記憶體中,如果記憶體不夠怎麼辦?為了處理這種情況,又誕生一些 Hash Join 的變種,比如 Grace Hash Join 。簡單說是通過分區方式實作,根據關聯字段将兩個表的資料分區,然後對同一分區的資料再進行原生 Hash join 的 build 與 probe 過程,最後将所有分區的資料合并成最後的結果集。當然在實際中會更複雜,比如在大資料量的情況下,有機率出現不同資料的 HASH 值卻是相同的問題。

總的來說,Hash Join 是處理大表間 Join 的不錯選擇。MySQL 在 8.0.18 前一直沒有 Hash join 的實作,甚至在5.5以前隻有最原始的 NLJ,5.5後才有 NLJ 優化變種的 B(Block)NLJ。但 Oracle 早在7.3版本之後就引入了 Hash join 算法,在 OLAP 領域中 Hash join 更是絕對的标配,Greenplum 和 Spark SQL 就充分利用了它。但是它也有缺點,比如隻能使用等值查詢、需要更多的記憶體資源等。

Merge Join

Merge Join ,準确地說它叫 Sort Merge Join, 在合并關聯查詢時要先確定兩個關聯表是按關聯字段相同排序的。如果關聯字段有可用的索引(配合聚集索引服用效果更佳)并且排序一緻,則可以直接進行Merge 操作,否則要先對關聯表按照關聯字段進行一次排序。排好序後,再從每個表取一條記錄開始比對,如果符合關聯條件,則放入結果集中;否則将關聯字段值較小的記錄抛棄,從這條記錄對應的表中取下一條記錄繼續進行比對,直到整個循環結束。是以它的成本是這樣的:

COST(MJ) = Read(M) + Sort(M) + Read(N) + Sort(N)

顯然,Merge Join 适合在關聯列上有索引的表,最好在關聯列還有相同的排序方式,在這種情況下它的關聯查詢效率是最高的。但是關聯字段如果沒有排序,那麼它的排序階段則比較耗時。

總結

通過前文的分析,我們基本可以回答文章最開頭的幾個問題了,更多資訊可以看下表格。另外,除了上述常見的三種資料庫Join方式外,還有 Hive 支援 Map Join 和 Reduce Join。

程式員必備的資料庫知識 2:Join 算法

常見 Join 算法的優勢對比總覽

作者

司馬遼太傑是 NineData 工程師。NineData 向企業和個人提供高效、安全的資料庫 SQL 開發、資料庫備份、資料複制/遷移/內建、資料對比等功能,是一個 SaaS 服務開箱即用,可以快速提升企業 SQL 開發效率,保障企業資料安全。NineData 位址:NineData-讓每個人用好資料和雲-玖章算術

繼續閱讀