【MySQL精通之路】SQL优化(1)-查询优化(4)-Hash联接查询

主博客:

【MySQL精通之路】SQL优化(1)-查询优化-CSDN博客

上一篇:

【MySQL精通之路】SQL优化(1)-查询优化(3)-索引合并-CSDN博客

下一篇:

【MySQL精通之路】SQL优化(1)-查询优化(5)-引擎条件下推-CSDN博客


默认情况下,MySQL(8.0.18及更高版本)尽可能使用Hash散列联接 。可以使用BNLNO_BNL优化器提示中的一个来控制是否使用散列联接

或者通过设置optimizer_switch服务器系统变量 中的block_nested_loop=onblock_nested_roop=off 为来控制是否采用Hash联接。

注意:

MySQL 8.0.18 支持在optimizer_switch 中设置hash_join标志,以及优化器提示HASH_JOIN和NO_HASH_JOIN。

在MySQL 8.0.19及更高版本中,这些都不再有任何效果。

从MySQL 8.0.18开始,MySQL对任何查询都使用散列联接,其中每个联接都有一个等联接条件,并且其中没有可应用于任何联接条件的索引,例如以下查询:

SELECT *
    FROM t1
    JOIN t2
        ON t1.c1=t2.c1;

当有一个或多个索引可用于单表谓词时,也可以使用散列联接。

散列联接通常比MySQL的早期版本中使用的块嵌套循环算法

(请参见块嵌套循环联接算法)

更快

从MySQL 8.0.20开始,删除了对块嵌套循环 的支持,并且使用散列联接替代块嵌套循环

在刚刚显示的示例和本节中的其余示例中,我们假设使用以下语句创建了三个表t1、t2和t3:

sql 复制代码
CREATE TABLE t1 (c1 INT, c2 INT);
CREATE TABLE t2 (c1 INT, c2 INT);
CREATE TABLE t3 (c1 INT, c2 INT);

您可以看到,使用EXPLAIN使用了散列联接,如下所示:

sql 复制代码
mysql> EXPLAIN
    -> SELECT * FROM t1
    ->     JOIN t2 ON t1.c1=t2.c1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: t2
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where; Using join buffer (hash join)

(在MySQL 8.0.20之前,有必要包含FORMAT=TREE选项 ,以查看Hash联接是否用于给定联接。)

EXPLAIN ANALYZE 还显示有关所使用的Hash联接的信息。

Hash联接 也用于涉及多个联接的查询,只要每对表至少有一个联接条件是等联接,就像这里显示的查询一样:

sql 复制代码
SELECT * FROM t1
    JOIN t2 ON (t1.c1 = t2.c1 AND t1.c2 < t2.c2)
    JOIN t3 ON (t2.c1 = t3.c1);

在如刚才所示的使用内联接 的情况下,任何非等联接的额外条件都将在执行联接后作为过滤器应用。 (对于外部联接 ,如左联接半联接反联接,它们被打印为联接的一部分。)这可以在EXPLAIN的输出中看到:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE
    -> SELECT *
    ->     FROM t1
    ->     JOIN t2
    ->         ON (t1.c1 = t2.c1 AND t1.c2 < t2.c2)
    ->     JOIN t3
    ->         ON (t2.c1 = t3.c1)\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (t3.c1 = t1.c1)  (cost=1.05 rows=1)
    -> Table scan on t3  (cost=0.35 rows=1)
    -> Hash
        -> Filter: (t1.c2 < t2.c2)  (cost=0.70 rows=1)
            -> Inner hash join (t2.c1 = t1.c1)  (cost=0.70 rows=1)
                -> Table scan on t2  (cost=0.35 rows=1)
                -> Hash
                    -> Table scan on t1  (cost=0.35 rows=1)

从刚刚显示的输出中也可以看出,多个Hash联接可以(并且)用于具有多个等联接条件的联接。

在MySQL 8.0.20之前,如果任何一对联接的表都没有至少一个等联接条件,则不能使用哈希联接,并且使用较慢的块嵌套循环算法 。在MySQL 8.0.20及更高版本中,这种情况下会使用散列联接,如下所示:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE
    -> SELECT * FROM t1
    ->     JOIN t2 ON (t1.c1 = t2.c1)
    ->     JOIN t3 ON (t2.c1 < t3.c1)\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t1.c1 < t3.c1)  (cost=1.05 rows=1)
    -> Inner hash join (no condition)  (cost=1.05 rows=1)
        -> Table scan on t3  (cost=0.35 rows=1)
        -> Hash
            -> Inner hash join (t2.c1 = t1.c1)  (cost=0.70 rows=1)
                -> Table scan on t2  (cost=0.35 rows=1)
                -> Hash
                    -> Table scan on t1  (cost=0.35 rows=1)

(本节稍后将提供其他示例。)

散列联接也适用于笛卡尔乘积------也就是说,当没有指定联接条件时,如图所示:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE
    -> SELECT *
    ->     FROM t1
    ->     JOIN t2
    ->     WHERE t1.c2 > 50\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join  (cost=0.70 rows=1)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Hash
        -> Filter: (t1.c2 > 50)  (cost=0.35 rows=1)
            -> Table scan on t1  (cost=0.35 rows=1)

在MySQL 8.0.20及更高版本中,联接不再需要包含至少一个等联接条件才能使用哈希联接。这意味着可以使用哈希联接优化的查询类型包括以下列表中的查询类型(带示例):

非等-内连接:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 JOIN t2 ON t1.c1 < t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t1.c1 < t2.c1)  (cost=4.70 rows=12)
    -> Inner hash join (no condition)  (cost=4.70 rows=12)
        -> Table scan on t2  (cost=0.08 rows=6)
        -> Hash
            -> Table scan on t1  (cost=0.85 rows=6)

半连接:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 
    ->     WHERE t1.c1 IN (SELECT t2.c2 FROM t2)\G
*************************** 1. row ***************************
EXPLAIN: -> Hash semijoin (t2.c2 = t1.c1)  (cost=0.70 rows=1)
    -> Table scan on t1  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t2  (cost=0.35 rows=1)

反联接:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t2 
    ->     WHERE NOT EXISTS (SELECT * FROM t1 WHERE t1.c1 = t2.c1)\G
*************************** 1. row ***************************
EXPLAIN: -> Hash antijoin (t1.c1 = t2.c1)  (cost=0.70 rows=1)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t1  (cost=0.35 rows=1)

1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1276
Message: Field or reference 't3.t2.c1' of SELECT #2 was resolved in SELECT #1

左外连接:

sql 复制代码
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Left hash join (t2.c1 = t1.c1)  (cost=0.70 rows=1)
    -> Table scan on t1  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t2  (cost=0.35 rows=1)

右外联接(注意MySQL将所有右外联接重写为左外联接):

sql 复制代码
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 RIGHT JOIN t2 ON t1.c1 = t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Left hash join (t1.c1 = t2.c1)  (cost=0.70 rows=1)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t1  (cost=0.35 rows=1)

默认情况下,MySQL 8.0.18及更高版本**尽可能使用散列联接。**可以使用BNL和NO_BNL优化器提示之一来控制是否使用散列联接。

(MySQL 8.0.18支持hash_join=onhash_join=off 作为optimizer_switch服务器系统变量设置的一部分,以及优化器提示HASH_JOIN 或NO_HASH_JOIN。在MySQL 8.0.19及更高版本中,这些不再有任何作用。)

散列联接的内存使用可以使用join_buffer_size系统变量进行控制;

哈希联接使用的内存不能超过这个数量。

当哈希连接所需的内存超过可用量时,MySQL会使用磁盘上的文件来处理。

如果发生这种情况,您应该注意,如果哈希联接无法放入内存 ,并且它创建的文件数超过了为open_files_limit设置的文件数,则联接可能不会成功。

为避免此类问题,请进行以下任一更改:

增加join_buffer_size,使散列联接不会溢出到磁盘。

增加open_files_limit。

从MySQL 8.0.18开始,哈希连接连接缓冲区递增分配的;

因此,您可以将join_buffer_size设置 得更高,而不需要小查询分配大量RAM ,但外部联接会分配整个缓冲区

在MySQL 8.0.20及更高版本中,散列联接也用于外部联接(包括反联接和半联接),因此这不再是问题。

补充:

博主PS:

上文内容来自官网,刚读的时候肯定会懵逼。

那Hash联接的存在意义和功能是什么呢?

我们知道连表查询的时候,我们会以on 某个字段 语句来连接两张表。

hash联接就是数据库在这里建立了一张hash表,用于存放这个on的条件结果,

那么循环到下一个关联记录的时候,如果还是相同的关联条件的时候,就直接从hash表里定位结果。

一般建立小表的hash映射。比如小表是1000条记录,大表是1万条记录。小表我如果做hash了查询小表的时间复杂度是不是O(1)?大表没有hash我的查找是不是O(n)。两表连接查,就是1万次for循环。

大小表要是**循环嵌套的方式连表判断,**时间复杂度是不是就O(n^2),就是1000万次查询。

但是这里我小表hash计算位置了。那时间复杂度就只有大表的O(n)。你可以理解为双层for循环,变成单层了。

这就是Hash联接查询的意义。

当然也可以两表都建立hash。只是会耗费hash计算时间,和内存而已。但是查询更快。

相关推荐
梵法利亚33 分钟前
Ubuntu-docker安装mysql
mysql·ubuntu·docker
炬火初现1 小时前
Etcd的安装与使用
数据库·etcd
IT猿手1 小时前
2025最新群智能优化算法:云漂移优化(Cloud Drift Optimization,CDO)算法求解23个经典函数测试集,MATLAB
开发语言·数据库·算法·数学建模·matlab·机器人
程序员爱钓鱼1 小时前
Go 语言高效连接 MySQL 数据库:从入门到实战
后端·mysql·go
雷渊1 小时前
深入分析理解mysql的MVCC
java·数据库·面试
Paparazi灬1 小时前
RocksDB写流程各种场景下的处理逻辑和线程交互时序
数据库
白熊一号2 小时前
Hi, DeepSeek 带我通过实战学习SQL入门知识
sql·mysql
神经星星2 小时前
【vLLM 教程】使用 TPU 安装
数据库·人工智能·机器学习
hjehheje3 小时前
clickhouse查询效率低
数据库·人工智能
七七powerful3 小时前
ClickHouse 中出现 DB::Exception: Too many parts 错误
java·前端·数据库