1. 程式人生 > 實用技巧 >宇智波程式筆記6-【MySQL】之join演算法詳解

宇智波程式筆記6-【MySQL】之join演算法詳解

在阿里巴巴的java開發手冊有這麼一條強制規定:超過三個表禁止join,需要join的欄位,資料型別保持絕對一致,多表關聯查詢時,要保證被關聯的欄位需要有索引。為什麼儘量避免使用join?如果使用join,我們應該怎麼用呢?接下來我們就一起聊一聊關於join的幾種演算法。

Simple Nested-Loop Join

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

在這個演算法中,需要對t1進行全表掃描,假設t1表1000行資料,那麼需要對t2表進行1000次全表掃描,假設t2表也是1000行資料,那麼就需要掃描1000 X 1000=1000000行。

示例圖如下:當t1表5行資料,t2表5行資料時,需要掃描25行資料。



Index Nested-Loop Join

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 Join


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,這裡暫不做介紹。

  /// <summary>
  
  /// 獲取當前頁面 或 功能 的路由地址
  
  /// </summary>
  
  /// <param name=www.jintianxuesha.com"Context"></param>
  
  /// <returns>www.yachengyl.cn www.yixingylzc.cn </returns>
  
  public string GetPageUrl(ActionExecutingContext Context, ref string areaName,ref string controllerName, ref string actionName) {
  
  areaName www.yixinpt2.cn= www.baihuayl7.cn Context.ActionDescriptor.RouteValues["area"].ToString();
  
  if (Context.ActionDescriptor.RouteValues.ContainsKey(www.jucaiyle.cn"controller"))
  
  controllerName www.feihongyul.cn www.tianyueptgw.cn= Context.ActionDescriptor.RouteValues["controller"].ToString();
  
  if (Context.www.javachenglei.com www.lecaixuangj.cn ActionDescriptor.RouteValues.ContainsKey( www.lecaixuan2.com www.xingchenylzc.cn"action")www.qilzixyouyi.com)
  
  actionName =www.tyyleapp.com www.jinmazx.cn www.fudayulpt.cn Context.ActionDescriptor.RouteValues["action"].ToString();

如何優化join的速度呢,這裡給出如下幾點建議:

    • 儘量避免使用join。
    • 用小表作為驅動表,減少外層迴圈的次數。
    • 多表關聯查詢時,要保證被關聯的欄位要有索引。
    • 適當增大join_buffer_size的值,快取的資料越多,就越能減少被驅動表掃描的次數。
    • 減少不必要的欄位查詢。
    • 需要join的欄位,資料型別保持絕對一致。