默认情况下,MySQL 在可能的情况下使用哈希连接。可以使用 BNL
和 NO_BNL
优化器提示来控制是否使用哈希连接,或者通过设置 block_nested_loop=on
或 block_nested_loop=off
作为优化器开关服务器系统变量的一部分。
MySQL 对于每个查询使用哈希连接,其中每个连接都有一个等值连接条件,并且没有索引可以应用于任何连接条件,例如以下情况:
SELECT *
FROM t1
JOIN t2
ON t1.c1=t2.c1;
哈希连接也可以在有一个或多个索引可以用于单表谓词的情况下使用。
哈希连接通常比块嵌套循环算法(见 块嵌套循环连接算法)更快,并且旨在取代 MySQL 早期版本中的块嵌套循环算法。块嵌套循环支持在 MySQL 8.0 中被删除,MySQL 8.3 服务器在以前使用块嵌套循环的地方使用哈希连接。
在本节的示例中,我们假设已经使用以下语句创建了三个表 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
来查看哈希连接的使用,例如:
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)
(本节后面提供了更多示例。)
哈希连接也可以用于笛卡尔积,即没有指定连接条件的情况,例如:
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 在可能的情况下使用哈希连接。可以使用 BNL
和 NO_BNL
优化器提示来控制是否使用哈希连接。
哈希连接的内存使用可以使用 join_buffer_size
系统变量来控制;哈希连接不能使用超过该金额的内存。当哈希连接需要的内存超过可用金额时,MySQL 将使用磁盘文件。如果发生这种情况,您应该注意,如果哈希连接不能 fit 到内存中并创建了超过 open_files_limit
设置的文件数,那么连接可能不会成功。要避免这种问题,可以采取以下两种方法:
-
增加
join_buffer_size
,以便哈希连接不溢出到磁盘。 -
增加
open_files_limit
。
哈希连接的连接缓冲区是增量分配的;因此,可以将 join_buffer_size
设置得更高,而不需要小查询分配大量 RAM。外连接也使用哈希连接,因此这不再是一个问题。