很長一段時間,MySQL 執行 鏈接 的惟一算法是
嵌套循環算法
( nested loop algorithm) 的變體 ,可是嵌套循環算法
在某些場景下很是低效,也是 MySQL 一直被詬病的一個問題。html
隨着 MySQL 8.0.18 的發佈,MySQL Server 可使用哈希鏈接
(hash join),這篇文章將會簡單介紹下哈希鏈接
如何實現,看看在 MySQL 中它是如何工做的,什麼時候使用它,有什麼限制。mysql
哈希鏈接是一種用於關係型數據庫中的鏈接算法,只能用於有等鏈接條件的鏈接中(on a.b = c.b)。它一般比 嵌套循環 算法 更高效(探測端很是很是小除外),尤爲是在沒有命中索引的狀況下。算法
簡單來講,哈希鏈接算法就是先把一張小表加載到內存哈希表裏,而後遍歷大表的數據,逐行去哈希表中匹配符合條件的數據,返回到客戶端。sql
(哈希表只是示例,方面理解,實際 hash 的 key 是鏈接的值,value 是數據行鏈表)數據庫
一般將 哈希鏈接 分爲兩個階段,構建階段(build phase)和探測階段(probe phase)。在構建階段,先選擇合適的表做爲「構建輸入」,構建哈希表,而後再依次遍歷另外一個「探測輸入」表記錄去探測哈希表查找符合鏈接條件的記錄。bash
以上圖爲例,查詢城市對應的省份。咱們假設 city 爲 構建輸入,在構建階段,服務器構建一個 city 哈希 表 ,遍歷 city 表,將行依次放進 哈希表,鍵爲 hash(province_id),值爲對應的 城市行。`服務器
在探測階段,服務器開始從 探測輸入(province) 讀取行。對於每一行都使用 hash(province.province_id) 值做爲查找鍵探測哈希表以匹配行。session
也就是,構建輸入能所有被加載到內存的狀況下,僅掃每一個探測行一次,使用常數時間查找就能夠查找到兩個輸入之間匹配的行。oop
將 構建輸入 所有加載到內存中無疑是效率最高的,但在有些狀況下,內存不足以將整張表加載到內存中,就須要分批來處理。sqlserver
常見的作法有兩種:
這種方式會致使探測輸入全表被掃描屢次。
MySQL 會選擇兩個輸入中較小的一個做爲構建輸入(以字節計算),在內存足夠的狀況下將構建輸入加載到內存處理,不夠的狀況下使用寫入文件的方式處理。
可使用 join_buffer_size
系統變量控制 哈希鏈接 的內存使用,哈希鏈接 使用的內存不能超過這個數量,當超過這個數量時,MySQL 將使用文件來處理。
若是內存超過 join_buffer_size
,而且文件超過 open_files_limit
,執行可能失敗。
可使用以下兩個解決方案:
join_buffer_size
來避免 哈希鏈接 溢出到磁盤open_files_limit
在 MySQL 8.0.18 版本中,若是使用一個或多個等鏈接條件將錶鏈接在一塊兒,而且沒有可用於鏈接條件的索引,將使用哈希鏈接。若是索引可用,MySQL 傾向於使用索引查找來支持嵌套循環。
默認狀況下,MySQL 會盡量使用哈希鏈接 ,能夠經過如下兩種方式啓用或關閉:
設置全局或 session 變量 (hash_join = on or hash_join = off);
SET optimizer_switch="hash_join=off";
複製代碼
使用 hints (HASH_JOIN or NO_HASH_JOIN)。
咱們將使用如下查詢做爲示例:
EXPLAIN FORMAT = tree
SELECT
city.name AS city_name,
province.name AS province_name
FROM
city
JOIN province
ON city.province_id = province.province_id;
複製代碼
輸出爲:
| -> Inner hash join (city.province_id = province.province_id) (cost=1333.82 rows=1329)
-> Table scan on city (cost=0.14 rows=391)
-> Hash
-> Table scan on province (cost=3.65 rows=34)
複製代碼
哈希鏈接 也能夠用到多個 join 的查詢中,只要存在等值鏈接,就可使用哈希鏈接。
例如如下查詢:
EXPLAIN FORMAT= TREE
SELECT
city.name AS city_name,
province.name AS province_name,
country.name AS country_name
FROM
city
JOIN province
ON city.province_id = province.province_id
AND city.id < 50
JOIN country
ON province.province_id = country.id
複製代碼
輸出爲:
| -> Inner hash join (city.province_id = country.id) (cost=23.27 rows=2)
-> Filter: (city.id < 50) (cost=5.32 rows=5)
-> Index range scan on city using PRIMARY (cost=5.32 rows=49)
-> Hash
-> Inner hash join (province.province_id = country.id) (cost=4.00 rows=3)
-> Table scan on province (cost=0.59 rows=34)
-> Hash
-> Table scan on country (cost=0.35 rows=1)
複製代碼
哈希鏈接也一樣適用於 「笛卡爾積」,即沒有指定查詢條件,以下:
EXPLAIN FORMAT= TREE
SELECT
*
FROM
city
JOIN province;
複製代碼
輸出爲:
| -> Inner hash join (cost=1333.82 rows=13294)
-> Table scan on city (cost=1.17 rows=391)
-> Hash
-> Table scan on province (cost=3.65 rows=34)
複製代碼
目前 MySQL 哈希鏈接只支持內鏈接,反鏈接、半鏈接和外鏈接仍然使用塊嵌套循環執行。
若是索引可用,MySQL 會更傾向於使用索引查找來支持嵌套循環;
當不存在等值查詢時,會使用嵌套循環。
以下:
EXPLAIN FORMAT=TREE
SELECT
*
FROM
city
JOIN province
ON city.province_id < province.province_id;
複製代碼
輸出爲:
| <not executable by iterator executor>
複製代碼
EXPLAIN FORMAT= TREE
在 MySQL 8.0.16 及以後的版本可使用,TREE 提供了相似於樹的輸出,對查詢處理的描述比傳統格式更加精確,它是惟一顯示 哈希鏈接 用法的格式。
除此以外,也可使用 EXPLAIN ANALYZE
查看 哈希鏈接 信息。
歡迎對 MySQL 有興趣的朋友一塊兒學習交流。