主博客:
【MySQL精通之路】SQL优化(1)-查询优化-CSDN博客
上一篇:
【MySQL精通之路】SQL优化(1)-查询优化(3)-索引合并-CSDN博客
下一篇:
【MySQL精通之路】SQL优化(1)-查询优化(5)-引擎条件下推-CSDN博客
默认情况下,MySQL(8.0.18及更高版本)尽可能使用Hash散列联接。可以使用BNL和NO_BNL优化器提示中的一个来控制是否使用散列联接
或者通过设置optimizer_switch服务器系统变量中的block_nested_loop=on或block_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 t1JOIN t2ON t1.c1=t2.c1;
当有一个或多个索引可用于单表谓词时,也可以使用散列联接。
散列联接通常比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使用了散列联接,如下所示:
mysql> EXPLAIN-> SELECT * FROM t1-> JOIN t2 ON t1.c1=t2.c1\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: t1partitions: NULLtype: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 1filtered: 100.00Extra: NULL
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: t2partitions: NULLtype: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 1filtered: 100.00Extra: Using where; Using join buffer (hash join)
(在MySQL 8.0.20之前,有必要包含FORMAT=TREE选项,以查看Hash联接是否用于给定联接。)
EXPLAIN ANALYZE还显示有关所使用的Hash联接的信息。
Hash联接也用于涉及多个联接的查询,只要每对表至少有一个联接条件是等联接,就像这里显示的查询一样:
SELECT * FROM t1JOIN 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)
从刚刚显示的输出中也可以看出,多个Hash联接可以(并且)用于具有多个等联接条件的联接。
在MySQL 8.0.20之前,如果任何一对联接的表都没有至少一个等联接条件,则不能使用哈希联接,并且使用较慢的块嵌套循环算法。在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: -> 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: NoteCode: 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 8.0.18及更高版本尽可能使用散列联接。可以使用BNL和NO_BNL优化器提示之一来控制是否使用散列联接。
(MySQL 8.0.18支持hash_join=on或hash_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计算时间,和内存而已。但是查询更快。