很长一段时间,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 有兴趣的朋友一块儿学习交流。