一. Join語法概述
join 用於多表中字段之間的聯繫,語法如下:
#... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditiona
table1:左表;table2:右表。
JOIN 依功能大致分為如下三類:
INNER JOIN(內連接,或等值連接):取得兩個表中存在連接匹配關係的記錄。
LEFT JOIN(左連接):取得左表(table1)完全記錄,即是右表(table2)並無對應匹配記錄。
RIGHT JOIN(右連接):與 LEFT JOIN 相反,取得右表(table2)完全記錄,即是左表(table1)並無匹配對應記錄。
注意:mysql不支援Full join,不過可以透過UNION 關鍵字來合併LEFT JOIN 與RIGHT JOIN來模擬FULL join.
先看一下實驗的兩張表:
表comments,總行數28856
表comments_for,總行數57,comments_id是有索引的,ID列為主鍵。
以上兩張表是我們測試的基礎,然後看一下索引,comments_for這個表comments_id是有索引的,ID為主鍵。
最近被公司某一開發問道JOIN了MySQL JOIN的問題,細數之下發下我對MySQL JOIN的理解並不是很深刻,所以也查看了很多文檔,最後在InsideMySQL公眾號看到了兩篇關於JOIN的分析,感覺寫的太好了,拿出來分享我對JOIN的實際測試吧。以下先介紹一下MySQL關於JOIN的演算法,總共分為三種(來源為InsideMySQL):
MySQL是只支援一種JOIN演算法Nested-Loop Join(嵌套循環連結),不像其他商業資料庫可以支援哈希連結和合併連接,不過MySQL的Nested-Loop Join(嵌套循環連結)也是有很多變種,能夠幫助MySQL更有效率的執行JOIN操作:
#(1)Simple Nested- Loop Join(圖片為InsideMySQL取來)
#這個演算法相對來說就是很簡單了,從驅動表中取出R1匹配S表所有列,然後R2,R3,直到將R表中的所有數據匹配完,然後合併數據,可以看到這種演算法要對S表進行RN次訪問,雖然簡單,但是相對來說開銷還是太大了
(2)Index Nested-Loop Join,實作方式如下圖:
#索引巢狀聯繫由於非驅動表上有索引,所以比較的時候不再需要一筆記錄進行比較,而可以透過索引來減少比較,從而加速查詢。這也就是平常我們在做關聯查詢的時候必須要求關聯欄位有索引的一個主要原因。
這種演算法在連結查詢的時候,驅動表會根據關聯字段的索引進行查找,當在索引上找到了符合的值,再回表進行查詢,也就是只有當匹配到索引以後才會進行回表。至於驅動表的選擇,MySQL優化器一般情況下是會選擇記錄數少的作為驅動表,但是當SQL特別複雜的時候不排除會出現錯誤選擇。
在索引巢狀連結的方式下,如果非驅動表的關聯鍵是主鍵的話,這樣來說效能就會非常的高,如果不是主鍵的話,關聯起來如果傳回的行數很多的話,效率就會特別的低,因為要多次的回表操作。先關聯索引,再根據二級索引的主鍵ID進行回表的運算。這樣來說的話性能相對就會很差。
(3)Block Nested-Loop Join,實作如下:
#在有索引的情況下,MySQL會嘗試去使用Index Nested -Loop Join演算法,在某些情況下,可能Join的列就是沒有索引,那麼這時MySQL的選擇絕對不會是最先介紹的Simple Nested-Loop Join演算法,而是會優先使用Block Nested-Loop Join的演算法.
Block Nested-Loop Join對比Simple Nested-Loop Join多了一個中間處理的過程,也就是join buffer,使用join buffer將驅動表的查詢JOIN相關列都給緩衝到了JOIN BUFFER當中,然後批次與非驅動表進行比較,這也來實現的話,可以將多次比較合併到一次,降低了非驅動表的訪問頻率。也就是只需要存取一次S表。這樣來說的話,就不會出現多次存取非驅動表的情況了,也只有這種情況下才會造訪join buffer。
在MySQL當中,我們可以透過參數join_buffer_size來設定join buffer的值,然後再進行操作。預設情況下join_buffer_size=256K,在尋找的時候MySQL會將所有的需要的列快取到join buffer當中,包括select的列,而不是只快取關聯列。在一個有N個JOIN關聯的SQL當中會在執行時候分配N-1個join buffer。
上面介紹完了,下面看一下特定的列子
(1)全表JOIN
EXPLAIN SELECT * FROM comments gc
JOIN comments_for gcf ON gc.comments_id=gcf.comments_id;
登入後複製
#看一下輸出訊息:
#可以看到在全表掃描的時候comments_for 作為了驅動表,此事因為關聯欄位是有索引的,所以對索引idx_commentsid進行了一個全索引掃描去匹配非驅動表comments ,每次能夠匹配到一行。此時使用的就是Index Nested-Loop Join,透過索引進行了全表的匹配,我們可以看到因為comments_for 表的量級遠小於comments ,所以說MySQL優先選擇了小表comments_for 作為了驅動表。
(2)全表JOIN+篩選條件
SELECT * FROM comments gc
JOIN comments_for gcf ON gc.comments_id=gcf.comments_id
WHERE gc.comments_id =2056
登入後複製
#
#此時使用的是Index Nested-Loop Join,先對驅動表comments 的主鍵進行篩選,符合一條,對非驅動表comments_for 的索引idx_commentsid進行seek匹配,最終匹配結果預計為影響一條,這樣就是僅僅對非驅動表的idx_commentsid索引進行了一次存取操作,效率相對來說還是非常高的。
(3)看關聯欄位是沒有索引的情況:
EXPLAIN SELECT * FROM comments gc
JOIN comments_for gcf ON gc.order_id=gcf.product_id
登入後複製
##我們來看看執行計畫:
從執行計畫我們就可以看出,這個表JOIN就是使用了Block Nested-Loop Join來進行表格關聯,先把comments_for (只有57行)這個小表作為驅動表,然後將comments_for 的需要的數據緩存到JOIN buffer當中,批量對comments 表進行掃描,也就是只進行一次匹配,前提是join buffer足夠大能夠存下comments_for的緩存數據。
而且我們看到執行計劃當中已經很明確的提示:Using where; Using join buffer (Block Nested Loop)
一般情況出現這種情況就證明我們的SQL需要優化了。
要注意的是這種情況下,MySQL也會選擇Simple Nested-Loop Join這種暴力的方法,我還沒搞懂他這個優化器是怎麼選擇的,但一般是使用Block Nested -Loop Join,因為CBO是基於開銷的,Block Nested-Loop Join的性能相對於Simple Nested-Loop Join是要好很多的。
(4)看看left join
EXPLAIN SELECT * FROM comments gc
LEFT JOIN comments_for gcf ON gc.comments_id=gcf.comments_id
登入後複製
#
看看執行計畫:
##### ###########這種情況,由於我們的關聯欄位是有索引的,所以說Index Nested-Loop Join,只不過當沒有篩選條件的時候會選擇第一張表作為驅動表去進行JOIN,去關聯非驅動表的索引進行Index Nested-Loop Join。 ######如果加上篩選條件gc.comments_id =2056的話,這樣就會篩選出一條對非驅動表進行Index Nested-Loop Join,這樣效率是很高的。 ######如果是下面這種:#########
EXPLAIN SELECT * FROM comments_for gcf
LEFT JOIN comments gc ON gc.comments_id=gcf.comments_id
WHERE gcf.comments_id =2056
登入後複製
###### #########透過gcf表進行篩選的話,就會預設選擇gcf表作為驅動表,因為很明顯他進行過了篩選,匹配的條件會很少,具體可以看下執行計劃:#########此,join基本上已經很明了,未完待續中,歡迎大家指出錯誤,我會認真改正。 。 。 。 ############ ####
以上是詳解MySQL JOIN原理介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!