1. 程式人生 > 其它 >mysql join語句的執行流程是怎麼樣的

mysql join語句的執行流程是怎麼樣的

mysql join語句的執行流程是怎麼樣的

join語句是使用十分頻繁的sql語句,同樣結果的join語句,寫法不同會有非常大的效能差距。

select * from t1 straight_join t2 on (t1.a=t2.a);a欄位都有索引

  1. TRAIGHT_JOIN語法能指定使用左邊的表作為join語句的驅動表,join是讓執行器自動選擇。以上語句會選擇t1作為驅動表。

  2. join語句,mysql內部執行時候會採用2中演算法。一個是NLJ(Index Nested-Loop Join)。一個是BNL(Block Nested-Loop Join)

  3. NLJ:在join語句執行過程中,如果可以使用到被驅動表的索引,我們稱之為“Index Nested-Loop Join”,簡稱 NLJ。

  4. 驅動表是走全表掃描,而被驅動表是走樹搜尋,所以驅動錶行數越小越好。掃描行數多,效能影響更大,因此應該讓小表來做驅動表。

  5. 如果驅動表有索引,被驅動表沒有索引,這種情況下,驅動表全表掃描後,去被驅動表中匹配where語句的條件,在被驅動表找一條資料又是全表掃描。這樣整個join掃描行數會內指數級別擴大。這種叫“Simple Nested-Loop Join”演算法。

  6. 基於第五點,這種情況太笨重。所以msql沒有采用”Simple Nested-Loop Join”演算法,而是叫“Block Nested-Loop Join”的演算法,簡稱 BNL。被驅動表沒有索引情況下,他的邏輯流程是這樣的:

    1. 把表 t1 的資料讀入執行緒記憶體 join_buffer 中,由於我們這個語句中寫的是 select *,因此是把整個表 t1 放入了記憶體;

    2. 掃描表 t2,把表 t2 中的每一行取出來,跟 join_buffer 中的資料做對比,滿足 join 條件的,作為結果集的一部分返回。

    1. explain語句查詢出來會有use join buffer (block nested loop)關鍵字

    2. join語句採用BNL演算法,雖然對錶 t1(100行) 和 t2(1000行) 都做了一次全表掃描,因此總的掃描行數是1100。由於 join_buffer 是以無序陣列的方式組織的,因此對錶 t2 中的每一行,都要做 100 次判斷,總共需要在記憶體中做的判斷次數是:100*1000=10 萬次。對比simple Nested-Loop Join演算法他是在記憶體中做對比計算。能大大提供效能。

    3. join_buffer 的大小是由引數join_buffer_size設定的,預設值是 256k。如果放不下表 t1 的所有資料話,策略很簡單,就是分段放。就是放多少先處理多少先作為結果集返回,然後清空join_buffer,繼續讀取後面的資料。

    4. 所以考慮到join_buffer大小有限,讓小表作為驅動表,分段情況下,分段次數少。也應該讓小表作為驅動表。

    5. 在決定哪個表做驅動表的時候,應該是兩個表按照各自的條件過濾,過濾完成之後,計算參與 join 的各個欄位的總資料量,資料量小的那個表,就是“小表”,應該作為驅動表。

    join語句優化:

    1. mysql在join語句時,內部做了一些優化,即:Multi-Range Read 優化 (MRR)。這個優化的主要目的是儘量使用順序讀盤。原理是:mysql的索引資料目錄中,都是有序的,我們讀入資料後,按主鍵排下序。這樣就極大可能在磁碟是順序讀盤。這引入了read_rnd_buffer ,它的大小是由 read_rnd_buffer_size 引數控制的。

    2. 如果你想要穩定地使用 MRR 優化的話,需要設定set optimizer_switch="mrr_cost_based=off"。(官方文件的說法,是現在的優化器策略,判斷消耗的時候,會更傾向於不使用 MRR,把 mrr_cost_based 設定為 off,就是固定使用 MRR 了。)explain語句也會有use MRR關鍵字

    3. 在使用BNL演算法時候,引擎是一行一行讀取資料。這樣就用不上MRR演算法優化,所以採取了BKA (Batched Key Access)演算法。他可以一次性從驅動表多讀一些資料,這些資料臨時放在join_buff中。(之前BNL演算法用不上join_buff,就利用了這個空間)。

    4. NBL演算法優化後的BKA演算法後,執行流程如下:

    5. 如果 join buffer 放不下 P1~P100 的所有資料,就會把這 100 行資料分成多段執行上圖的流程。如果要使用 BKA 優化演算法的話,你需要在執行 SQL 語句之前,先設定set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';前兩個引數的作用是要啟用 MRR。這麼做的原因是,BKA 演算法的優化要依賴於 MRR。

    6. BNL演算法資料太大,稍不主機就會極大影響mysql服務效能,導致Buffer Pool命中率變低。大表 join 操作雖然對 IO 有影響,但是在語句執行結束後,對 IO 的影響也就結束了。但是,對 Buffer Pool 的影響就是持續性的,需要依靠後續的查詢請求慢慢恢復記憶體命中率。

    7. BNL 演算法對系統的影響主要包括三個方面:

      1. 可能會多次掃描被驅動表,佔用磁碟 IO 資源;
      2. 判斷 join 條件需要執行 M*N 次對比(M、N 分別是兩張表的行數),如果是大表就會佔用非常多的 CPU 資源;
      3. 可能會導致 Buffer Pool 的熱資料被淘汰,影響記憶體命中率。
    8. BNL演算法優化:

      1. BNL 轉 BKA演算法,在驅動表和被驅動表建索引,如果不方便建索引(資料大,join語句不頻繁),可以人工主動使用臨時表中轉,拆分多個語句轉化成BKA演算法。
      2. hash join。條件匹配是n x m級別計算,如果 join_buffer 裡面維護的不是一個無序陣列,而是一個雜湊表的話,那麼就不是 10 億次判斷,而是 100 萬次 hash 查詢。mysql不支援雜湊 join。並且,MySQL 官方的 roadmap,也是遲遲沒有把這個優化排上議程。備註:mysql8.0已經支援

執行流程:

  1. 從表 t1 中讀入一行資料 R;
  2. 從資料行 R 中,取出 a 欄位到表 t2 裡去查詢;
  3. 取出表 t2 中滿足條件的行,跟 R 組成一行,作為結果集的一部分;
  4. 重複執行步驟 1 到 3,直到表 t1 的末尾迴圈結束。