MySQL的JOIN到底是怎么玩的
高手回答
在MySQL中,查詢操作通常會涉及到聯結不同表格,而JOIN命令則在這一過程中扮演了關鍵角色。在JOIN操作中,我們通常會使用三種不同的方式,分別是內連接、左連接以及右連接。
- INNER JOIN(內連接,或稱為等值連接):此操作獲取了兩個表中字段相互匹配的記錄,實質上是取得了這兩個表的交集部分。
- LEFT JOIN(左連接):相較于內連接,左連接獲取了左表格的所有記錄,即便在右表格中可能沒有對應的匹配記錄。這樣,查詢結果將包含兩個表格的交集部分,以及左表格中的所有數據。
- RIGHT JOIN(右連接):右連接與左連接相反,它主要用于獲取右表格中的所有記錄,即便在左表格中找不到對應的匹配數據。因此,RIGHT JOIN同樣會取得兩個表格的交集部分,以及右表格中的所有數據。
在實施JOIN操作時,還常常會搭配上關鍵字ON,用以明確指定關聯查詢的一些條件。
嵌套循環算法
MySQL通常采用嵌套循環(Nested-Loop Join)的方法來執行關聯查詢,具體而言,主要包括簡單嵌套循環連接(Simple Nested Loop Join)、塊狀嵌套循環連接(Block Nested Loop Join)和索引嵌套循環連接(Index Nested Loop Join)這三種算法。
然而,這三種算法的效率均未能達到特別的高水平。
- 簡單嵌套循環:該算法直截了當,通過全面掃描連接兩張表來進行逐一數據比對,因此其復雜度可以被視為N*M,其中N是驅動表的數量,而M是被驅動表的數量。
- 索引嵌套循環:如果內循環表中的字段具有索引,索引嵌套循環會利用該索引來查詢數據。由于索引是基于B+樹的,因此復雜度近似為N*logM。
- 塊狀嵌套循環:這種算法引入了一個緩沖區(Buffer),它會提前將外循環的一部分結果存放在JOIN BUFFER中,然后內循環中的每一行都與整個緩沖區中的數據進行比較。盡管比較次數仍為N*M,但由于JOIN BUFFER是基于內存的,因此效率大大提高。
盡管MySQL已經盡力優化這些算法,但這幾種算法的復雜度仍然相對較高。這也是為何不建議在數據庫中頻繁進行多表JOIN的原因。隨著表格數量和數據量的增加,JOIN操作的效率會指數級下降。
當無法使用JOIN進行關聯查詢時,可以考慮使用子查詢、臨時表或者聯合查詢等方式來實現相同的查詢需求。
如果不能通過數據庫做關聯查詢,那么需要查詢多表的數據的時候要怎么做呢?
主要有兩種做法:
- 在內存中自己做關聯,即先從數據庫中把數據查出來之后,我們在代碼中再進行二次查詢,然后再進行關聯。
- 數據冗余,那就是把一些重要的數據在表中做冗余,這樣就可以避免關聯查詢了。
- 寬表,就是基于一定的join關系,把數據庫中多張表的數據打平做一張大寬表,可以同步到ES或者干脆直接在數據庫中直接查都可以
若無法通過數據庫進行關聯查詢,處理涉及多表數據的情況,常見的做法有兩種:
- 在內存中自行關聯:首先從數據庫中檢索數據,然后在程序中執行第二次查詢,隨后進行關聯操作。
- 數據冗余:通過在表中存儲一些重要數據的冗余副本,可以避免進行關聯查詢。
- 寬表設計:基于一定的連接關系,將數據庫中多個表的數據打平形成一個龐大的寬表,這個寬表可以同步到Elasticsearch(ES),或者直接在數據庫中進行查詢操作。
MySQL的Hash Join是什么?
在MySQL 8.0中新增的 Hash Join 算法是一種用于多表連接的算法。在此之前,MySQL通常使用嵌套循環(Nested-Loop Join)的方法來執行關聯查詢,然而嵌套循環算法在性能方面并不理想。因此,引入了 Hash Join 算法,旨在優化 Nested-Loop Join 的性能表現。
所謂的 Hash Join 實際上底層利用了哈希表。
Hash Join 是針對等值連接場景的優化方法,其基本原則是將驅動表的數據加載到內存中,并構建哈希表,這樣只需遍歷一次非驅動表,然后通過哈希查找在哈希表中尋找匹配的行,就能完成連接操作。
舉個例子:
在上述的 left join SQL 中,在進行 Hash Join 過程時,主要包括兩個步驟:構建和探測。
在構建階段中,如果優化器經過優化選擇了 employee 作為驅動表,那么就會將該驅動表的數據構建到哈希表中:
圖片
在探測階段,當從 company 表中取出記錄后,會到哈希表中查詢匹配的數據,然后進行聚合操作。
圖片
需要注意的是,上述提到的哈希表是存在于內存中的。然而,內存是有限的(受到 join_buffer_size 的限制)。那么,如果內存無法容納驅動表的數據怎么處理呢?那就不得不說一說基于磁盤的Hash Join了。
基于磁盤的Hash Join
基于磁盤的哈希連接
當驅動表中的數據量較大,無法一次性加載到內存中時,就需要考慮將數據存儲在磁盤上。通過將哈希表的部分內容存儲在磁盤上,可以分批加載和處理數據,減少對內存的需求。
在這種算法中,為了避免一個大型哈希表無法完全存儲在內存中,可以采用分表的方法來解決。即通過哈希算法將驅動表分割成多個片段,并將臨時分片寫入磁盤。
這意味著將一個驅動表拆分成多個哈希表,并分別存儲在磁盤上。
圖片
接下來是進行連接操作,在這個過程中,對被驅動表也會使用相同的哈希算法進行分區,以確定在哪個分區中。在確定分區后,首先要確認該分區是否已經被加載到內存中,如果已加載,則可以直接在內存中的哈希表中查找匹配的行。
圖片
如果哈希值對應的分區尚未加載到內存中,則需要從磁盤上讀取該分區的數據到內存中的哈希表,并進行匹配。
這樣不斷重復進行,直至完成所有數據的連接操作,然后返回結果集。





























