1. 程式人生 > >MySQL JOIN原理(轉)

MySQL JOIN原理(轉)

對比 bsp 過程 mage 性能 速查 sin code 相對

先看一下實驗的兩張表:

表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原理(轉)