#### 8.2.1.4 哈希连接优化

默认情况下，MySQL（8.0.18 及更高版本）尽可能使用散列连接。可以使用以下方法之一来控制是否使用散列连接[`BNL`](optimizer-hints.html#optimizer-hints-table-level)和[`NO_BNL`](optimizer-hints.html#optimizer-hints-table-level)优化器提示，或通过设置[`block_nested_loop=on`](switchable-optimizations.html#optflag_block-nested-loop)要么`block_nested_loop=关闭`作为优化器设置的一部分\_切换服务器系统变量。

笔记

MySQL 8.0.18 支持设置一个[`hash_join`](switchable-optimizations.html#optflag_hash-join)标志在[`优化器开关`](server-system-variables.html#sysvar_optimizer_switch)，以及优化器提示[`HASH_JOIN`](optimizer-hints.html#optimizer-hints-table-level)和`NO_HASH_JOIN`.在 MySQL 8.0.19 及更高版本中，这些都不再有效。

从 MySQL 8.0.18 开始，MySQL 对任何查询使用哈希连接，每个连接都有一个等连接条件，并且没有索引可以应用于任何连接条件，例如这个：

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

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

散列连接通常比块嵌套循环算法更快并且旨在用于这种情况，而不是块嵌套循环算法（请参阅[块嵌套循环加入算法](nested-loop-joins.html#block-nested-loop-join-algorithm)) 在以前版本的 MySQL 中使用。从 MySQL 8.0.20 开始，删除了对块嵌套循环的支持，并且服务器在以前使用块嵌套循环的任何地方都使用哈希连接。

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

```
CREATE TABLE t1 (c1 INT, c2 INT);
CREATE TABLE t2 (c1 INT, c2 INT);
CREATE TABLE t3 (c1 INT, c2 INT);
```

您可以看到正在使用散列连接[`解释`](explain.html)， 像这样：

```
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 之前，必须包含`格式=树`选项来查看哈希连接是否被用于给定的连接。）

[`解释分析`](explain.html#explain-analyze)还显示有关使用的散列连接的信息。

散列连接也用于涉及多个连接的查询，只要每对表的至少一个连接条件是等连接，如下所示的查询：

```
SELECT * FROM t1
    JOIN t2 ON (t1.c1 = t2.c1 AND t1.c2 < t2.c2)
    JOIN t3 ON (t2.c1 = t3.c1);
```

在刚刚显示的使用内部连接的情况下，任何不是等连接的额外条件都将在执行连接后作为过滤器应用。（对于外连接，例如左连接、半连接和反连接，它们被打印为连接的一部分。）这可以在输出中看到`解释`：

```
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)
```

从刚刚显示的输出中也可以看出，多个哈希连接可以（并且正在）用于具有多个等连接条件的连接。

在 MySQL 8.0.20 之前，如果任何一对连接表没有至少一个 equi-join 条件，则无法使用哈希连接，并且采用了较慢的块嵌套循环算法。在 MySQL 8.0.20 及更高版本中，在这种情况下使用哈希连接，如下所示：

```
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)
```

（本节后面将提供其他示例。）

散列连接也适用于笛卡尔积——也就是说，当没有指定连接条件时，如下所示：

```
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 及更高版本中，连接不再需要包含至少一个等连接条件才能使用哈希连接。这意味着可以使用哈希连接优化的查询类型包括以下列表中的类型（带有示例）：

-   *内部非等连接*：

    ```
    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)
    ```

-   *半连接*：

    ```
    mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1
        ->     WHERE t1.c1 IN (SELECT t2.c2 FROM t2)\G
    *************************** 1. row ***************************
    EXPLAIN: -> Nested loop inner join
        -> Filter: (t1.c1 is not null)  (cost=0.85 rows=6)
            -> Table scan on t1  (cost=0.85 rows=6)
        -> Single-row index lookup on <subquery2> using <auto_distinct_key> (c2=t1.c1)
            -> Materialize with deduplication
                -> Filter: (t2.c2 is not null)  (cost=0.85 rows=6)
                    -> Table scan on t2  (cost=0.85 rows=6)
    ```

-   *反加入*：

    ```
    mysql> EXPLAIN FORMAT=TREE SELECT * FROM t2
        ->     WHERE NOT EXISTS (SELECT * FROM t1 WHERE t1.col1 = t2.col1)\G
    *************************** 1. row ***************************
    EXPLAIN: -> Nested loop antijoin
        -> Table scan on t2  (cost=0.85 rows=6)
        -> Single-row index lookup on <subquery2> using <auto_distinct_key> (c1=t2.c1)
            -> Materialize with deduplication
                -> Filter: (t1.c1 is not null)  (cost=0.85 rows=6)
                    -> Table scan on t1  (cost=0.85 rows=6)
    ```

-   *左外连接*：

    ```
    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=3.99 rows=36)
        -> Table scan on t1  (cost=0.85 rows=6)
        -> Hash
            -> Table scan on t2  (cost=0.14 rows=6)
    ```

-   *右外连接*（观察 MySQL 将所有右外连接重写为左外连接）：

    ```
    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=3.99 rows=36)
        -> Table scan on t2  (cost=0.85 rows=6)
        -> Hash
            -> Table scan on t1  (cost=0.14 rows=6)
    ```

    默认情况下，MySQL 8.0.18 及更高版本尽可能使用散列连接。可以使用以下方法之一来控制是否使用散列连接[`BNL`](optimizer-hints.html#optimizer-hints-table-level)和`NO_BNL`优化器提示。

    （支持 MySQL 8.0.18[`hash_join=on`](switchable-optimizations.html#optflag_hash-join)要么[`hash_join=off`](switchable-optimizations.html#optflag_hash-join)作为设置的一部分[`优化器开关`](server-system-variables.html#sysvar_optimizer_switch)服务器系统变量以及优化器提示[`HASH_JOIN`](optimizer-hints.html#optimizer-hints-table-level)要么[`NO_HASH_JOIN`](optimizer-hints.html#optimizer-hints-table-level).在 MySQL 8.0.19 及更高版本中，这些不再有任何影响。）

    哈希连接的内存使用可以使用[`加入缓冲区大小`](server-system-variables.html#sysvar_join_buffer_size)系统变量；哈希连接不能使用超过此数量的内存。当散列连接所需的内存超过可用的数量时，MySQL 通过使用磁盘上的文件来处理这个问题。如果发生这种情况，您应该知道，如果哈希连接无法放入内存并且它创建的文件多于设置的文件，则连接可能不会成功[`open_files_limit`](server-system-variables.html#sysvar_open_files_limit).为避免此类问题，请进行以下任一更改：

-   增加`加入缓冲区大小`这样哈希连接就不会溢出到磁盘。

-   增加`open_files_limit`.

    从 MySQL 8.0.18 开始，哈希连接的连接缓冲区是增量分配的；因此，您可以设置[`加入缓冲区大小`](server-system-variables.html#sysvar_join_buffer_size)在没有分配大量 RAM 的小查询的情况下更高，但外部连接分配整个缓冲区。在 MySQL 8.0.20 及更高版本中，散列连接也用于外连接（包括反连接和半连接），因此这不再是问题。
