技術分享 | MySQL 的 join_buffer_size 在內鏈接上的應用

做者:楊濤濤

本文詳細介紹了 MySQL 參數 join_buffer_size 在 INNER JOIN 場景的使用,OUTER JOIN 不包含。在討論這個 BUFFER 以前,咱們先了解下 MySQL 的 INNER JOIN 分類。html

若是按照檢索的性能方式來細分,那麼不管是兩表 INNER JOIN 仍是多表 INNER JOIN,都大體能夠分爲如下幾類:mysql

1.JOIN KEY 有索引,主鍵
2.JOIN KEY 有索引, 二級索引
3.JOIN KEY 無索引算法

今天主要針對第三種場景來分析,也是就全表掃的場景。sql

回過頭來看看什麼是 join_buffer_size?

JOIN BUFFER 是 MySQL 用來緩存以上第2、第三這兩類 JOIN 檢索的一個 BUFFER 內存區域塊。通常建議設置一個很小的 GLOBAL 值,完了在 SESSION 或者 QUERY 的基礎上來作一個合適的調整。好比 默認的值爲 512K, 想要臨時調整爲 1G,那麼json

mysql>set session join_buffer_size = 1024 * 1024 * 1024; 
mysql>select * from ...;    
mysql>set session join_buffer_size=default;    
或者
mysql>select /*+  set_var(join_buffer_size=1G) */ * from ...;

接下來詳細看下 JOIN BUFFER 的用法。那麼 MySQL 裏針對 INNER JOIN 大體有如下幾種算法,緩存

1. Nested-Loop Join 翻譯過來就是嵌套循環鏈接,簡稱 NLJ。

這種是 MySQL 裏最簡單的,最容易理解的表關聯算法。session

好比拿語句 select * from p1 join p2 using (r1) 來講,oop

先從表 p1 裏拿出來一條記錄 ROW1,完了再用 ROW1 遍歷表 p2 裏的每一條記錄,而且字段 r1 來作匹配是否相同以便輸出;再次循環剛纔的過程,直到兩表的記錄數對比完成爲止。 性能

那看下實際 SQL 的執行計劃翻譯

mysql> explain format=json select * from p1 inner join p2 as b using(r1)\G
*************************** 1. row ***************************
EXPLAIN: {
 "query_block": {
   "select_id": 1,
   "cost_info": {
     "query_cost": "1003179606.87"
   },
   "nested_loop": [
     {
       "table": {
         "table_name": "b",
         "access_type": "ALL",
         "rows_examined_per_scan": 1000,
         "rows_produced_per_join": 1000,
         "filtered": "100.00",
         "cost_info": {
           "read_cost": "1.00",
           "eval_cost": "100.00",
           "prefix_cost": "101.00",
           "data_read_per_join": "15K"
         },
         "used_columns": [
           "id",
           "r1",
           "r2"
         ]
       }
     },
     {
       "table": {
         "table_name": "p1",
         "access_type": "ALL",
         "rows_examined_per_scan": 9979810,
         "rows_produced_per_join": 997981014,
         "filtered": "10.00",
         "cost_info": {
           "read_cost": "5198505.87",
           "eval_cost": "99798101.49",
           "prefix_cost": "1003179606.87",
           "data_read_per_join": "14G"
         },
         "used_columns": [
           "id",
           "r1",
           "r2"
         ],
         "attached_condition": "(`ytt_new`.`p1`.`r1` = `ytt_new`.`b`.`r1`)"
       }
     }
   ]
 }
}
1 row in set, 1 warning (0.00 sec)

從上面的執行計劃來看,表 p2 爲第一張表(驅動表或者叫外表),第二張表爲 p1,那 p2 須要遍歷的記錄數爲 1000,同時 p1 須要遍歷的記錄數大概 1000W 條,那這條 SQL 要執行完成,就得對錶 p1(內表)匹配 1000 次,對應的 read_cost 爲 5198505.87。那如何才能減小表 p1 的匹配次數呢?那這個時候 JOIN BUFFER 就派上用處了

2. Block Nested-Loop Join ,塊嵌套循環,簡稱 BNLJ

那 BNLJ 比 NLJ 來講,中間多了一塊 BUFFER 來緩存外表的對應記錄從而減小了外表的循環次數,也就減小了內表的匹配次數。仍是那上面的例子來講,假設 join_buffer_size 恰好能容納外表的對應 JOIN KEY 記錄,那對錶 p2 匹配次數就由 1000 次減小到 1 次,性能直接提高了 1000 倍。

咱們看下用到 BNLJ 的執行計劃

mysql> explain format=json select * from p1 inner join p2 as b using(r1)\G
*************************** 1. row ***************************
EXPLAIN: {
 "query_block": {
   "select_id": 1,
   "cost_info": {
     "query_cost": "997986300.01"
   },
   "nested_loop": [
     {
       "table": {
         "table_name": "b",
         "access_type": "ALL",
         "rows_examined_per_scan": 1000,
         "rows_produced_per_join": 1000,
         "filtered": "100.00",
         "cost_info": {
           "read_cost": "1.00",
           "eval_cost": "100.00",
           "prefix_cost": "101.00",
           "data_read_per_join": "15K"
         },
         "used_columns": [
           "id",
           "r1",
           "r2"
         ]
       }
     },
     {
       "table": {
         "table_name": "p1",
         "access_type": "ALL",
         "rows_examined_per_scan": 9979810,
         "rows_produced_per_join": 997981014,
         "filtered": "10.00",
         "using_join_buffer": "Block Nested Loop",
         "cost_info": {
           "read_cost": "5199.01",
           "eval_cost": "99798101.49",
           "prefix_cost": "997986300.01",
           "data_read_per_join": "14G"
         },
         "used_columns": [
           "id",
           "r1",
           "r2"
         ],
         "attached_condition": "(`ytt_new`.`p1`.`r1` = `ytt_new`.`b`.`r1`)"
       }
     }
   ]
 }
}
1 row in set, 1 warning (0.00 sec)

上面的執行計劃有兩點信息,

第一:多了一條 "using_join_buffer": "Block Nested Loop"

第二:read_cost 這塊由以前的 5198505.87 減小到 5199.01

3. 最近 MySQL 8.0.18 發佈,終於推出了新的 JOIN 算法 — HASH JOIN。

MySQL 的 HASH JOIN 也是用了 JOIN BUFFER 來作緩存,可是和 BNLJ 不一樣的是,它在 JOIN BUFFER 中之外表爲基礎創建一張哈希表,內表經過哈希算法來跟哈希表進行匹配,hash join 也就是進一步減小內表的匹配次數。固然官方並無說明詳細的算法描述,以上僅表明我的臆想。那仍是針對以上的 SQL,咱們來看下執行計劃。

mysql> explain format=tree select * from p1 inner join p2 as b using(r1)\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (p1.r1 = b.r1)  (cost=997986300.01 rows=997981015)
   -> Table scan on p1  (cost=105.00 rows=9979810)
   -> Hash
       -> Table scan on b  (cost=101.00 rows=1000)

1 row in set (0.00 sec)

經過上面的執行計劃看到,針對表 p2 創建一張哈希表,而後針對表 p1 來作哈希匹配。

目前僅僅支持簡單查看是否用了 HASH JOIN,而沒有其餘更多的信息展現。

總結下,本文主要討論 MySQL 的內表關聯在沒有任何索引的低效場景。其餘的場景另外開篇。

相關文章
相關標籤/搜索