MySQL JOIN原理

最近被公司某一開發問道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次訪問,雖然簡單,可是相對來講開銷仍是太大了ide

(2)Index Nested-Loop Join,實現方式以下圖:oop

索引嵌套連接因爲非驅動表上有索引,因此比較的時候再也不須要一條條記錄進行比較,而能夠經過索引來減小比較,從而加速查詢。這也就是平時咱們在作關聯查詢的時候必需要求關聯字段有索引的一個主要緣由。性能

這種算法在連接查詢的時候,驅動表會根據關聯字段的索引進行查找,當在索引上找到了符合的值,再回表進行查詢,也就是隻有當匹配到索引之後纔會進行回表。至於驅動表的選擇,MySQL優化器通常狀況下是會選擇記錄數少的做爲驅動表,可是當SQL特別複雜的時候不排除會出現錯誤選擇。測試

在索引嵌套連接的方式下,若是非驅動表的關聯鍵是主鍵的話,這樣來講性能就會很是的高,若是不是主鍵的話,關聯起來若是返回的行數不少的話,效率就會特別的低,由於要屢次的回表操做。先關聯索引,而後根據二級索引的主鍵ID進行回表的操做。這樣來講的話性能相對就會不好。優化

(3)Block Nested-Loop Join,實現以下:3d

在有索引的狀況下,MySQL會嘗試去使用Index Nested-Loop Join算法,在有些狀況下,可能Join的列就是沒有索引,那麼這時MySQL的選擇絕對不會是最早介紹的Simple Nested-Loop Join算法,而是會優先使用Block Nested-Loop Join的算法。blog

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基本上已經很明瞭了,未完待續中,歡迎你們指出錯誤,我會認真改正。。。。

相關文章
相關標籤/搜索