做者:楊濤濤
本文詳細介紹了 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 是 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 大體有如下幾種算法,緩存
這種是 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 就派上用處了
那 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
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 的內表關聯在沒有任何索引的低效場景。其餘的場景另外開篇。