MySQL連接(join)原理

一、MySQL JOIN分類

  • INNER JOIN,內連接,返回左右表互相匹配的所有行
  • LEFT JOIN,左外連接,返回左表的所有行,若某些行在右表裏沒有相對應的匹配行,則將右表的列在新表中置爲NULL
  • RIGHT JOIN,右外連接,返回右表的所有行,若某些行在左表裏沒有相對應的匹配行,則將左表的列在新表中置爲NULL
  • FULL JOIN,MySQL不支持,可以使用左外連接和右外連接的聯合查詢
  • CROSS JOIN,交叉連接

二、JOIN順序

inner join驅動順序由優化器自己指定,如果優化器選擇有誤可以使用straight_join自己指定驅動順序以達到優化的目的

left join驅動順序是固定的,left join左邊的表爲驅動表,右邊爲匹配表,RIGHT JOIN則剛好相反

下面這兩個SQL是等價的嗎?

select count(*) from t_user_log a left join t_user b on a.uid = b.uid and b.city = 78; 
select count(*) from t_user_log a left join t_user b on a.uid = b.uid where b.city = 78;

不等價,因爲前者在匹配表中加了過濾條件,而後者在關聯結果中加了過濾條件,前者不影響驅動表檢索出來的數據(與匹配表無法匹配的數據依然會檢索出來,只是匹配表字段部分值等於NULL),後者影響驅動表檢索出來的數據(因爲在結果集中直接被過濾掉了)。

三、MySQL原理介紹

場景: t1表插入100行,t2表插入1000行數據

select * from t1 straight_join t2 on (t1.a=t2.a);

Index Nested-Loop Join

連接字段a上有索引,straight_join 讓 MySQL 使用固定的連接方式執行查詢,這樣優化器只會按照我們指定的方式去 join,不會自己選擇。在這個語句裏,t1 是驅動表,t2 是被驅動表

執行流程:先遍歷表 t1,然後根據從表 t1 中取出的每行數據中的 a 值,去表 t2 中查找滿足條件的記錄。並且可以用上被驅動表的索引。驅動表是走全表掃描,而被驅動表是走樹搜索

  • 使用 join 語句,性能比強行拆成多個單表執行 SQL 語句的性能要好
  • 如果使用 join 語句的話,需要讓小表做驅動表,因爲小表是驅動表,走的是全表掃描,表越小,掃描的數據越少,性能越好

Simple Nested-Loop Join

被驅動表上沒有可用的索引

因爲t2沒有索引,所以需要全表掃描。總共需掃描100*1000行,數據量大的時候性能可想而知,爲此MySQL採取Block Nested-Loop Join策略優化。

Block Nested-Loop Join

流程:

  • 把表 t1 的數據讀入線程內存 join_buffer 中,由於我們這個語句中寫的是 select *,因此是把整個表 t1 放入了內存
  • 掃描表 t2,把表 t2 中的每一行取出來,跟 join_buffer 中的數據做對比,滿足 join 條件的,作爲結果集的一部分返回

Block Nested-Loop Join算法的這 10 萬次判斷是內存操作,速度上會快很多,性能也更好,並且只需要掃描兩次表即可

假設小表的行數是 N,大表的行數是 M,那麼在這個算法裏:

  1. 兩個表都做一次全表掃描,所以總的掃描行數是 M+N;
  2. 內存中的判斷次數是 M*N。

這裏無所謂驅動表和被驅動表

join_buffer放不下

join_buffer 的大小是由參數 join_buffer_size 設定的,默認值是 256k。如果放不下表 t1的所有數據話,策略很簡單,就是分段放

驅動表的數據行數是 N,需要分 K 段才能完成算法流程,被驅動表的數據行數是M,K 表示爲λ*N

在這個算法的執行過程中:

  1. 掃描行數是 N+λNM;
  2. 內存判斷 N*M 次

內存判斷次數是不受選擇哪個表作爲驅動表影響的。而考慮到掃描行數,在 M 和N 大小確定的情況下,N 小一些,整個算式的結果會更小。在數據量大的情況下,如果你的 join 語句很慢,可以把join_buffer_size 改大 ,這樣分段數小一點

N 越大,分段數 K 越大。那麼,N 固定的時候,什麼參數會影響 K 的大小呢?(也就是λ的大小)

答案是 join_buffer_size。join_buffer_size 越大,一次可以放入的行越多,分成的段數也就越少,對被驅動表的全表掃描次數就越少

結論:join_buffer放不下驅動表時,驅動表越小,掃描被驅動表的次數越少,總的掃描行數越少

能不能使用 join 語句?
推薦使用join字段上加索引方式,沒有索引的話性能較差。所以在判斷要不要使用 join 語句時,就是看 explain 結果裏面,Extra 字段裏面有沒有出現“Block Nested Loop”字樣 ,出現的話最好不要使用

如果要使用 join,應該選擇大表做驅動表還是選擇小表做驅動表?

  • 如果是 Index Nested-Loop Join 算法,應該選擇小表做驅動表;
  • 如果是 Block Nested-Loop Join 算法:
    • 在 join_buffer_size 足夠大的時候,是一樣的
    • 在 join_buffer_size 不夠大的時候(這種情況更常見),應該選擇小表做驅動表。

在決定哪個表做驅動表的時候,應該是兩個表按照各自的條件過濾,過濾完成之後,計算參與 join 的各個字段的總數據量,數據量小的那個表,就是“小表”,應該作爲驅動表

參考《MySQL45講》

發佈了140 篇原創文章 · 獲贊 18 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章