MySQL Nested-Loop Join 算法

2022-01-05 胡翱藍的網絡日誌

MySQL使用 Nested-Loop Join (NLJ) 算法來執行表之間的連接,NLJ有以下三種實現:

Simple Nested-Loop Join

Index Nested-Loop Join

Bolck Nested-Loop Join

通過下面這段 SQL 我們來分析下這三者的區別。

-- 創建表t1CREATE TABLE t1 (  id INT NOT NULL PRIMARY KEY,   a INT NOT NULL,   b INT NOT NULL,  KEY idx_a ( a ) ) ENGINE = INNODB;
-- 往t1裡初始化1000條數據delimiter;;CREATE PROCEDURE initdata () BEGIN DECLARE i INT; SET i = 1; WHILE i <= 1000 DO INSERT INTO t1 VALUES ( i, i, i ); SET i = i + 1; END WHILE;END;;declimiter;CALL initdata ();
-- 創建表t2,並插入100條數據CREATE TABLE t2 LIKE t1;INSERT INTO t2 ( SELECT * FROM t1 WHERE id <= 100 );
SELECT * FROM t1 INNER JOIN t2 ON t1.a = t2.a;

如上,t1 和 t2 都有主鍵索引 id 和索引 a,欄位 b 無索引。存儲過程 initdata() 往 t1 插入1000條數據,再往 t2 插入100條數據。最後 t1 和 t2 做了連表查詢,我特意用了 STRAIGHT_JOIN ,目的是讓優化器按照指定的連接方式執行查詢,如果用 INNER JOIN 的話,則優化器會以小表 t2 為驅動表,然後取每行欄位 a 去 t1 表的索引 idx_a 上做匹配,這樣會影響我們分析 SQL 的執行過程。

Index Nested-Loop Join


下圖是上面 join 語句的 explain 結果:

圖 1 使用索引欄位 join 的 explain 結果

可以看到,表 t1 是驅動表,而且做了全表掃描,被驅動表 t2 的欄位 a 上有索引,join 的過程中用上了這個索引,因此這條 SQL 的執行過程是:

從表 t1 讀入一條數據記為 R;

從 R 中取出 a 欄位,去 t2 表 a 欄位的索引 (idx_a) 中查找;

取出表 t2 中滿足條件的行,跟 R 組成一行,作為結果集的一部分;

重複執行步驟 1 到 3,直至表 t1 的末尾,循環結束。

這就是「Index Nested-Loop Join」 (NLJ) 算法的執行流程,整個過程中表 t1 做了全表掃描,需要掃描 1000 行。對於每一行 R,根據欄位 a 去表 t2 中查找,走的是樹搜索,因為數據是一一對應的,所以每次只掃描一行。因此掃描的總行數是 1100 行。

Simple Nested-Loop Join


現在,我們把 join 語句改成這樣:

SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.a = t2.b;

然後看看這條 SQL 的 explain 結果:

圖 2 不使用索引欄位 join 的 explain 結果


與 NLJ 不同的是,由於欄位 b 沒有建索引,所以 t1 每次去 t2 匹配的時候,都要做一次全表掃描,因此整個過程需要掃描的行數是 1000 * 100 = 10萬行,這個算法就是「Simple Nested-Loop Join」。

類似我們寫程序時的嵌套循環,將外層循環的數據傳遞到內層循環依次去匹配,只要還有表去 join,那麼這個過程就會循環下去。如果 t1 和 t2 都是 10萬行的表,就要掃描 100 億行,這個算法看上去太「笨重」了。

事實上,MySQL 並沒有使用 Simple Nested-Loop Join 算法,從圖2可以看到,表 t2 的 Extra 是 Using where; Using join buffer (Block Nested Loop),所以 MySQL 使用的是「Bolck Nested-Loog Join」算法,簡稱 BNL。



Bolck Nested-Loog Join

BNL 是通過緩存驅動表的數據來減少被驅動表的掃描次數,MySQL 官方文檔以「Join Buffer」來命名這個緩存。

算法的流程是:

將驅動表 t1 的數據讀入到線程內存 join buffer 中,由於 SQL 語句是 SELECT *,因此是把整行數據放入;

掃描被驅動表 t2,取出每行數據跟 join buffer 中的數據對比,滿足條件的,作為結果集的一部分。

與 Simple Nested-Loop Join 將外層循環的數據傳遞到內層循環依次去匹配的思想不同,BNL 是掃描被驅動表然後與 join buffer 中緩存的驅動表的數據做匹配,所以整個過程總共掃描的行數是 1000 + 100 = 1100 行,數據匹配過程中,需要判斷 1000 * 100 = 10 萬次,因為是內存操作,所以速度會快很多,性能也更好。

在實際應用場景下,join buffer 可能放不下驅動表的數據。join buffer 的大小是由參數 join_buffer_size 設置的,默認是256k。如果放不下驅動表所有數據,MySQL的策略是分段放。

如果 join buffer 放不下 t1 的所有數據,執行過程就變成:

將驅動表 t1 的數據讀入到線程內存 join buffer 中,假設到第 800 行 join buffer 滿了;

掃描被驅動表 t2,取出每行數據跟 join buffer 中的數據對比,滿足條件的,作為結果集的一部分;

清空 join buffer;

繼續掃描 t1,把剩下的 200 行數據放入到 join buffer 中,然後繼續執行第 2 步。

小結


這篇我介紹了 MySQL 執行 join 語句可能使用的三種算法,這三種算法主要是由能否使用被驅動表的索引決定的。通過對三種算法的執行流程,可以得到以下結論:

能否用上被驅動表的索引,對 join 語句的性能影響很大;

不能使用被驅動表的索引,只能使用 Bolck Nested-Loog Join 算法,這樣的 SQL 儘量不要用,需要去優化。尤其是在大表上的 join 操作,可能要掃描被驅動表很多次,會佔用大量系統資源;

在使用 join 語句時,應該讓小表做驅動表。

Reference:

https://dev.mysql.com/doc/refman/5.6/en/nested-loop-joins.html

極客時間《MySQL實戰45講》

相關焦點

  • 【譯】Oracle調優技巧21:Nested Loop Outer Join
    如您所知,在nested loop equi-join 中,決定哪個表將成為驅動表是決定因素。通常,兩個表中的小表將被視為驅動表,這樣成本cost 就低了。[成本將會降低是因為只需執行最小的循環次數]。然而,在nested loop outer join 中,由於默認情況下將選擇 Parent 表作為驅動表,而不管它是小表還是大表,因此決策過程變得無關緊要。
  • MySQL 8.0 新特性:哈希連接(Hash Join)
    大多數情況下,hash join 比之前的 Block Nested-Loop 算法在沒有索引時的等值連接更加高效。loop 連接算法。接下來我們比較一下 hash join 和 block nested loop 的性能,首先分別為 t1、t2 和 t3 生成 1000000 條記錄:set join_buffer_size=2097152000;SET @@cte_max_recursion_depth = 99999999;INSERT INTO
  • MySQL 8.0 新特性:引人注目的哈希連接(Hash Join)
    大多數情況下,hash join 比之前的 Block Nested-Loop 算法在沒有索引時的等值連接更加高效。loop 連接算法。接下來我們比較一下 hash join 和 block nested loop 的性能,首先分別為 t1、t2 和 t3 生成 1000000 條記錄:set join_buffer_size=2097152000;SET @@cte_max_recursion_depth
  • MySQL join 學習
    因此 join 有等效別名關鍵字:inner join:join顯示(explicit) inner join 與隱式(implicit) inner join 性能上沒有區別。MySQL 支持如下三種 join 操作(以兩張表 join 為例):nested loop join:利用嵌套 for 循環對兩張表中的每一行數據進行兩兩比較。
  • 我想說:mysql 的 join 真的很弱
    可以把mysql當一個黑盒,使用角度來驗證這個結論) 驗證結論的時候,會有很多發現,各位往後看。三、 實驗環境:vmware10+centos7.4+mysql5.7.22 ,centos7內存4.5G,4核,50G硬碟。mysql配置為2G,特別說明硬碟是SSD。
  • mysql 如何優化left join
    於是我上網查了下MySQL實現join的原理,原來MySQL內部採用了一種叫做 nested loop join的算法。Nested Loop Join 實際上就是通過驅動表的結果集作為循環基礎數據,然後一條一條的通過該結果集中的數據作為過濾條件到下一個表中查詢數據,然後合併結果。
  • 飯可以多吃,SQL JOIN 要少用
    join的方式有Join算法面試官: 再給你個機會,如果讓你來實現Join算法你會怎麼做?(太慢了)Block nested loop當無法使用索引執行join操作的時候,InnoDB會自動使用Block nested loop 算法總結
  • SQL語句別再帶過多的JOIN了,這樣寫才優秀!
    ., 回去等通知吧再談SQL JOIN面試官:換個話題,談談你對join的理解我:好的(再答錯就徹底完了,把握住機會)回顧SQL中的join可以根據某些條件把指定的表給結合起來並將數據返回給客戶端join的方式有inner join 內連接
  • 靈魂拷問:你寫的SQL一般有幾個join ?​
    ., 回去等通知吧再談SQL Join面試官:換個話題,談談你對join的理解我:好的(再答錯就徹底完了,把握住機會)回顧SQL中的join可以根據某些條件把指定的表給結合起來並將數據返回給客戶端join的方式有inner join 內連接
  • 為什麼代碼規範要求SQL語句不要過多的join?
    Join算法面試官:再給你個機會,如果讓你來實現Join算法你會怎麼做?我:無索引的話,嵌套循環就完事了嗷。有索引的話,則可以利用索引來提升性能.面試官:說回join_buffer 你認為join_buffer裡面存儲的是什麼?
  • 面試官:你的SQL一般有幾個join?​
    ., 回去等通知吧再談SQL Join面試官:換個話題,談談你對join的理解我:好的(再答錯就徹底完了,把握住機會)回顧SQL中的join可以根據某些條件把指定的表給結合起來並將數據返回給客戶端join的方式有inner join 內連接
  • 阿里規定超過三張表禁止join,為啥?
    如果資料庫的性能無限強大,多個表的join肯定是需要的,尤其是複雜的分析型(OLAP)查詢,甚至可能涉及10幾個表的join,但現實是大部分資料庫的性能都太弱了,尤其是涉及到多表join的查詢。給@韓飛點個讚,國內懂這個做這個的太少了,以後就靠他們了。
  • 面試官靈魂一問: 為什麼 SQL 語句不要過多的 join?
    我:對於可以通過增加索引來優化join語句的執行速度 可以通過冗餘信息來減少join的次數 儘量減少表連接的次數,一個SQL語句表連接的次數不要超過5次面試官:可以總結為join語句是相對比較耗費性能,對嗎?
  • MySQL 8.0 新特性之Hash Join
    另一方面在8.0.18之前,MySQL只支持Nest Loop Join算法,MySQL針對這個算法做了若干優化,實現了Block NestLoop Join,Index NestLoop Join等,有了這些優化,在一定程度上能緩解對HashJoin的迫切程度。本文會介紹HashJoin的原理以及在使用和不使用HashJoin的情況下,性能的差異。
  • 我想說:mysql的join 真的很弱
    對比1.1 和5.1 步驟sql查詢,4表連接,對我本機mysql來說 ,1.5千萬數據查詢很流利,是一個mysql數據量流利分水嶺。(這個只是現象,不太準確,需要同時計算表的容量)。 步驟5.1對比6.1,6.2,6.3,多表join對mysql來說,處理有些吃力。
  • 【譯】Oracle調優技巧22:Hash Outer Join
    Eed loopLoop(父Hash表中所有未標記「MATCHED」的記錄) 返回查詢中提到的hash表中的列,同時查詢中提到的子探測表中的列返回NULL值。End loop需要記住的鮮為人知的事實/*+ USE_**HASH**(<<driving table>>) */ 是可以用來強制使用(impose)該 Hash outer join 的 hint。只有當兩個表都由outer join「(+)」操作符連接時,Oracle才會選擇這種方法。
  • MySQL中left join的幾個SQL對比
    mysql> select t1.id,t1.name from test1 t1 left join test2 t2 on t1.id=t2.id and t1.name='bb'; +----+-+| id | name |+----+-+| 1 | aa || 2 | bb || 3 | cc || 4 |
  • 神奇的SQL之聯表細節:MySQL JOIN的執行過程(一)
    MySQL 的聯表算法是基於嵌套循環算法(nested-loop algorithm)而衍生出來的一系列算法,根據不同條件而選用不同的算法1.這種算法簡單粗暴,但毫無性能可言,時間性能上來說是 n(表中記錄數) 的 m(表的數量) 次方,所以 MySQL 做了優化,聯表查詢的時候不會出現這種算法,即使在無 WHERE  條件且 ON 的連接鍵上無索引時,也不會選用這種算法
  • 阿里規定超過三張表禁止JOIN,為啥呢?
    可以把mysql當一個黑盒,使用角度來驗證這個結論) 驗證結論的時候,會有很多發現,各位往後看。 三、 實驗環境 vmware10+centos7.4+mysql5.7.22 centos7內存4.5G,4核,50G硬碟。
  • Oracle放大招:MySQL 即將支持 Hash Join
    在剛剛OOW19會上的《python and mysql 8.0 document store》topic中,終於看到了MySQL即將在8.0.18