2019 年 10 月 14 日 正式发布了 MySQL 8.0.18 GA 版本,带来了一些新特性和增强功能,其中最引人注目的是多表连接查询支持 hash join,有了Hash Join,SQL的性能将得到显著的提升.
MySQL Hash Join的特性介绍:
1、对于大数据量的表关联,hash join速度将明显比Block Nested Loop快很多
2、在内存中处理
3、必要情况下,会使用磁盘空间
4、用于内连接,可扩展到外连接、半连接和反连接
5、替换查询计划中的Block Nested Loop
6、可以通过HINT强制SQL走hash join或者Block Nested Loop
从MySQL 8.0.18开始,MySQL对任何查询都具有相等连接条件且不使用索引的查询使用哈希连接,例如:
SELECT *
FROM t1
JOIN t2
ON t1.c1=t2.c1;
大多数情况下,hash join 比之前的 Block Nested-Loop 算法在没有索引时的等值连接更加高效。使用以下语句创建三张测试表:
CREATE TABLE t1 (c1 INT, c2 INT);
CREATE TABLE t2 (c1 INT, c2 INT);
CREATE TABLE t3 (c1 INT, c2 INT);
使用EXPLAIN FORMAT=TREE命令可以看到执行计划中的 hash join,例如:
mysql> EXPLAIN FORMAT=TREE
SELECT *
FROM t1
JOIN t2
ON t1.c1=t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> 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)
要查看是否将散列连接用于给定的连接,必须EXPLAIN与 FORMAT=TREE选项一起使用。 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 FORMAT=TREE命令的输出进行查看:
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 join 连接。
但是,如果任何连接语句(ON)中没有使用等值连接条件,将不会采用 hash join 连接方式。例如:
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:
这时,将会采用性能更慢的 block nested loop 连接算法。这与 MySQL 8.0.18 之前版本中没有索引时的情况一样:
mysql> EXPLAIN
-> SELECT *
-> FROM t1
-> JOIN t2
-> ON (t1.c1 = t2.c1)
-> JOIN t3
-> ON (t2.c1 < t3.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 (Block Nested Loop)
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: t3
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 (Block Nested Loop)
Hash join 连接同样适用于不指定查询条件时的笛卡尔积(Cartesian product),例如:
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尽可能使用哈希联接。仅在MySQL 8.0.18中,可以通过以下两种方式之一控制使用哈希联接:
在全局或会话级别上,通过使用 hash_join=on或 hash_join=off作为optimizer_switch服务器系统变量设置的一部分。默认值为hash_join=on。
通过使用优化器提示之一HASH_JOIN 或 NO_HASH_JOIN作为给定联接的一部分,逐个案例地进行。
(从MySQL 8.0.19开始,hash_join 优化器开关以及 HASH_JOIN和 NO_HASH_JOIN优化器提示不再起作用。)
哈希联接的内存使用情况可以使用join_buffer_size系统变量进行控制 ;哈希联接不能使用超过此数量的内存。当散列连接所需的内存超过可用容量时,MySQL通过使用磁盘上的文件来处理。如果发生这种情况,您应该注意,如果哈希联接无法容纳到内存中并且创建的文件数超过设置的数量,联接可能不会成功open_files_limit 。为避免此类问题,请进行以下更改之一:
增加join_buffer_size以使哈希联接不会溢出到磁盘。
增加open_files_limit。
从MySQL 8.0.18开始,使用批处理密钥访问(BKA)的哈希连接的连接缓冲区是递增分配的;因此,对于使用增量分配的内部join_buffer_size联接BKA,您可以设置较高的值而无需使用少量查询来分配非常大的RAM,但是外部联接会分配整个缓冲区。在MySQL 8.0.20及更高版本中,哈希联接也用于外部联接(包括反联接和半联接),因此这不再是问题。