Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


10.2.1.4 Hash Join 优化

默认情况下,MySQL 会在可能的情况下使用哈希连接。可以使用BNLNO_BNL优化器提示或设置block_nested_loop=onblock_nested_loop=off来控制哈希连接的使用。

MySQL 在每个连接都有等值连接条件,并且没有可以应用于任何连接条件的索引时,会使用哈希连接,例如:

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

哈希连接也可以在存在一个或多个可以用于单表谓词的索引时使用。

在以下示例中,我们假设三个表t1t2t3已经使用以下语句创建:

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

可以通过使用EXPLAIN来查看哈希连接是否在使用,例如:

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)

EXPLAIN ANALYZE也会显示关于哈希连接的信息。

哈希连接用于涉及多个连接的查询,只要至少每对表中的一个连接条件是等值连接,像下面这个示例中一样:

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

在像上面所示的那种情况下,如果使用了内连接,那么任何额外的条件都将作为过滤器在连接执行后应用。 (对于外连接,如左连接、半连接和反连接,它们将被打印到连接中。) 这可以从EXPLAIN输出中看到:

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

(后续部分还提供了更多示例。)

哈希连接也用于笛卡尔积—that is,when no join condition is specified,像下面这个示例中一样:

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> 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: -> 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)
  • 反连接:

    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
  • 左外连接:

    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 将所有右外连接重写为左外连接):

    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 会尽量使用哈希连接。可以使用BNLNO_BNL优化器提示来控制哈希连接的使用。

可以使用join_buffer_size系统变量来控制哈希连接的内存使用;哈希连接不能使用超过这个amount的内存。当哈希连接所需的内存超过可用内存时,MySQL 会将其写入磁盘。如果发生这种情况,你应该注意的是,如果哈希连接无法在内存中完成,它可能会创建更多文件,而这些文件的数量将超出open_files_limit。为了避免这种问题,可以采取以下措施:

  • 增加join_buffer_size,以便哈希连接不需要写入磁盘。

  • 增加open_files_limit

哈希连接的缓冲区是按增量分配的,因此可以将join_buffer_size设置得更高,而不会导致小查询占用大量RAM,但是外部连接会占用整个缓冲区。哈希连接用于外部连接(包括反向连接和半连接)因此,这已经不再是一个问题了。