【MySQL】join的底層理解

前言

在日常開發中,只要寫了sql,難免會使用的join關鍵字連接兩個或多個表,在這裏還是先解釋一下inner join、left join、right join之間的區別以及驅動表的概念。

INNER JOIN:查詢兩個表之間的交集

取值時遵循笛卡爾乘積,即利用雙層循環遍歷兩個表的數據,若table1的結果集比較少,那麼就拿它當作外層循環,稱爲驅動表,外層循環每取一條數據,就拿該數據去內層循環table2表中匹配結果集,此時table2稱爲被驅動表

 

LEFT JOIN:取左表(驅動表)的全部數據,右表(被驅動表)如果有對應數據就顯示,沒有就爲NULL

 

RIGHT JOIN:取右表(驅動表)的全部數據,左表(被驅動表)如果有對應數據就顯示,沒有就顯示爲NULL

 

join連接查詢原理

相信大家理解了上面join的用法之後,都會發出這樣一個疑問:這效率豈不是低成鬼了?如果你有這種敏感的覺悟,那麼恭喜你被認證爲實打實的程序猿

       


但是我們一抓頭髮馬上就能料到,MySQL能活那麼久,至今還那麼受企業歡迎,怎麼會允許這種低效率的事情發生?所以這其中一定有鬼!

沒錯,MySQL使用了一種算法去優化它:Nested-Loop Join(嵌套循環連接),但是這個算法有三個變種,分別是

Simple Nested-Loop Join 簡單嵌套循環連接

Index Nested-Loop Join 索引嵌套循環連接

Block Nested-Loop Join  塊索引嵌套連接

 

簡單嵌套循環連接

所謂簡單嵌套循環連接,其實真的很簡單,就是啥都不做,利用循環嵌套對join的所有表逐一去遍歷。如下兩個表,以t1作爲驅動表,遍歷到元素a時,從被驅動表t2中匹配與a相等的行,並將匹配結果存儲到結果集中,這種方法的效率無疑是非常低的,其時間複雜度O(n) = t1 * t2,真的做了一次笛卡爾乘積。

上面的查詢邏輯的僞代碼如下

for(M id : main){
    for(S main_id : second){
        if(id==main_id){
            //添加到結果集
        }
    }
}

我們可以思考一下,這兩層循環可以怎麼去優化它呢?

說到mysql的優化,我們的第一個念頭肯定就是加索引。沒錯,我們可以通過減少循環次數來達到優化的效果。例如被驅動表t2中的字段加了索引,而這個字段剛好就是驅動表t1中遍歷的那個字段,那豈不是美滋滋?直接拿這個字段的值去被驅動表t2中取值不就得了。下面這個算法就是根據索引進行的優化

 

索引嵌套循環連接

如果上面的解釋還不是十分明白,我們可以通過僞代碼來理解

//假如有兩個表,主表main和從表second,主鍵均是id且second表的main_id加了索引
select m.* from main m
inner join second s on s.main_id = m.id

 

執行上面這條查詢語句時的取值代碼類似下面

for(M id : main){
    if(second.contains(id)){
        //添加到結果集
    }
}

從代碼層面來看,我們馬上就能感受到循環次數的量級變化,但是其實拿id去匹配被驅動表second時,還是會有一個回表的操作,降低了效率。

各位猿友可能又會問,什麼又是回表?

這涉及到索引的底層原理了,但是上了我這條船的人,船長我一定會給他講得明明白白。

比如有個表的狀態列state添加了索引,並存儲了0、1、2、3、4、5、6、7共8個元素,那麼它的索引就是下面的B+樹結構,其中最下面的葉子節點存儲的纔是真正的元素,而葉子節點除了存儲索引列本身的值外,還會存儲這行記錄的id,那麼我們又需要拿着這個id值從主鍵id的索引樹中找到這行記錄的所有元素,這個過程就稱爲回表查詢。

可見回表查詢降低了查詢效率

 

塊嵌套循環連接

如果join後面的條件不是索引列表怎麼辦呢?使用簡單嵌套循環連接是不可能的,這輩子都不可能的了

mysql使用了一個叫join buffer的緩衝區去減少循環次數,這個緩衝區默認是256KB,可以通過命令show variables like 'join_%'查看

其具體的做法是,將驅動表t1中符合條件的列一次性查詢到緩衝區中,然後遍歷一次被驅動表t2,並逐一和緩衝區的所有值比較,將比較結果加入結果集中

這裏直接用文字描述可能有點晦澀難懂,那還是舉一個例子,大家肯定立馬就懂了

假設t1表中有100行記錄,t2表中有50行記錄,而塊嵌套循環算法會每次讀取t1表中的10條記
錄,並加入到緩衝區buffer中。然後遍歷一次t2表,對於t2表中的每行記錄,都會與與buffer
中的10條記錄進行比較,並將相等的加入結果集。如此一來,循環次數變爲10*50=500次

​​​​​​​

                                                                                掃描下方二維碼 關注全棧船長

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章