【MySQL】之join算法詳解

    在阿里巴巴的java開發手冊有這麼一條強制規定:超過三個表禁止join,須要join的字段,數據類型保持絕對一致,多表關聯查詢時,要保證被關聯的字段須要有索引。
    爲何儘可能避免使用join?若是使用join,咱們應該怎麼用呢?接下來咱們就一塊兒聊一聊關於join的幾種算法。

Simple Nested-Loop Joinjava


    Simple Nested-Loop Join算法是指讀取驅動表t1中的每行數據,將每行數據傳遞到被驅動表t2上,取出被驅動表t2中知足條件的行,和t1組成結果集。算法

    在這個算法中,須要對t1進行全表掃描,假設t1表1000行數據,那麼須要對t2表進行1000次全表掃描,假設t2表也是1000行數據,那麼就須要掃描1000 X 1000=1000000行。sql

    示例圖以下:當t1表5行數據,t2表5行數據時,須要掃描25行數據。數據庫



Index Nested-Loop Joinapache


    index nested-loop join算法的優化思路是經過驅動表的匹配條件,直接與被驅動表的索引進行匹配,減小了被驅動表的掃描次數。緩存

    該算法一樣要對驅動表t1進行全表掃描,可是咱們在拿着t1表的數據去被驅動表t2進行匹配時能夠利用t2表的索引,若是t1表中1000行數據,t2表中1000行數據,那麼一共就須要掃描1000+1000=2000行數據。這個過程就跟咱們寫程序時的嵌套查詢相似,而且能夠用上被驅動表的索引,因此稱之爲「Index Nested-Loop Join」,簡稱 NLJ。微信

    示例以下:當t1表有5行數據,t2表有5行數據時,一共須要掃描5+5=10行數據。數據結構


Block Nested-Loop Joinoop


    Block Nested-Loop join,基於塊的嵌套循環,簡稱BNL算法,其優化思路主要是減小被驅動表的循壞次數,它會將驅動表的數據緩存起來,把參與查詢的列緩存到join buffer裏,而後拿join buffer裏的數據批量與內層表的數據在join buffer中進行匹配,知足join條件的,做爲結果集的一部分返回。
    能夠看到該算法對兩個表都進行了全表掃描,所以掃描的行數是兩個表的行數之和。這種場景下,雖然在掃描行數上和NLJ算法同樣,可是因爲BNL算法是在內存中進行判斷,速度上會快不少。
    join buffer的大小是由參數join_buffer_size設定,默認256k。若是一次放不下驅動表的全部數據,會分段放,這種狀況下會致使被驅動表掃描屢次。若是被驅動表是冷數據表,而且屢次掃描讀取被驅動表間隔超過1S的話,就會將他放入LRU鏈表的young區域,致使業務數據沒法進入熱數據區,減小了bufferpool的命中率,這又是另一個課題了,暫不過多展開。咱們能夠經過調大join_buffer_size來提升緩存的數據量,減小對被驅動表的掃描次數。
啓用BNL算法須要在optimizer_switch參數中設置block_nested_loop=on。

Batched Key Access性能


    BNL算法提高了join的性能,可是它在經過輔助索引鏈接後須要回表,就會消耗大量的隨機I/O,咱們知道隨機IO對MySQL的影響是很是大的。所以MySQL5.6引入了Batched Key Access(BKA,批量鍵訪問聯接)算法。
    再說BKA算法時不得不提的就是MySQL的Multi-Range Read 優化,MRR的目的主要是減小磁盤的隨機訪問。咱們都知道,Innodb索引採用的是B+tree的數據結構,數據保存在主鍵索引中,而且是按照主鍵遞增的順序插入的,可是二級索引的排列順序和主鍵的排列順序通常是不同的,它保存的主鍵值也並不是按照主鍵順序排列,在回表時就會出現隨機訪問主鍵索引的狀況。因此若是能夠按照主鍵遞增順序查詢的話,對磁盤的讀比較接近順序讀,這樣就可以提高讀性能。
    MRR優化的思路就是在進行範圍查詢時,在獲得主鍵值以後,先按照主鍵的順序進行排序,而後拿着排好序的主鍵ID再去主鍵索引進行查詢,這樣就能體現出順序性的優點了。由於是多值查詢,因此通常用於range、ref類型的查詢。
    再說會BKA算法,當被驅動表上有索引能夠利用時,那麼就在行提交給被 join 的表以前,先對兩個表的對應列的索引字段進行join,獲得主鍵值後,按照主鍵排好序後,利用 MRR 技術,批量訪問表取數據,減小了隨機 IO。可是若是被 join 的表沒用索引的話,那就只能使用BNL算法了。
    具體算法以下圖:
 
開啓BKA和MRR的方式:
   
   
    
    
             
    
    
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
MySQL在8.0版本已經實現了hash join,這裏暫不作介紹。

小結


如何優化join的速度呢,這裏給出以下幾點建議:
  • 儘可能避免使用join。
  • 用小表做爲驅動表,減小外層循環的次數。
  • 多表關聯查詢時,要保證被關聯的字段要有索引。
  • 適當增大join_buffer_size的值,緩存的數據越多,就越能減小被驅動表掃描的次數。
  • 減小沒必要要的字段查詢。
  • 須要join的字段,數據類型保持絕對一致。

本文分享自微信公衆號 - MySQL數據庫技術棧(Mysqltechnology)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索