Hash join算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hash join算法相关的知识,希望对你有一定的参考价值。

参考技术A

hash join的基本思想是根据晓得row sources(称作build input)建立一个可以存在于hash area内存中的hash table,然后用大的row sources(称作probe input)来探测前面所建的hash table。

如果hash area内存不够大,hash table就无法完全存放在hash area内存中。针对这种情况,oracle在连接键利用一个hash函数build input和probe input分割成许多个不相连的分区(分别记作Si和Bi);这个阶段就做分区阶段;然后各自相应的分区再做hash join,这个阶段叫做join阶段。

如果在分区后,针对某个分区所建的hash table还是太大的话,oracle就采用nested-loops hash join。nested-loops hash join就是对部分Si建立hash table,然后读取所有的bi与所建立hash table做连接,然后再对剩余的si建立hash table,再将所有bi与所建立的hash table做连接,直至所有的si都连接完了。

hash join算分有一个限制,它是在假设两张标在连接键上是均匀的,也就是每个分区拥有差不多的数据。但是实际当中数据都是不均匀的,为了很好的解决这个问题,oracle引进了几种技术: 位图向量过滤、角色互换、柱状图

14.如果分区活 后,最小的分区也比内存大,则发生nested- loop hash join

Cost(HJ)=Read(S)+ build hash table in memory(CPU)+Read(B) +
Perform In memory Join(CPU)

忽略cpu的时间,则
Cost(HJ)=Read(S)+Read(B)

Cost(HJ)=Cost(HJ1)+Cost(HJ2)

Cost(HJ1)的成本就是扫描S,B表,并将无法放在内存上的部分写回磁盘,对应前面第2步至第12步。
Cost(HJ2)即为做nested-loop hash join的成本,对应前面的第13步至第14步。
其中Cost(HJ1)近似等于Read(S)+Read(B)+Write((S-M)+(B-B*M/S))。

因为在做nested-loop hash join时,对每一chunk的build input,都需要读取整个probe input,因此
Cost(HJ2)近似等于Read((S-M)+n (B-B M/S))

其中n是nested-loop hash join需要循环的次数。

n=(S/F)/M

一般情况下,如果n大于10的话,hash join的性能将大大下降。从n的计算公式可以看出,n与Fan-out成反比例,提高fan-out,可以降低n。当hash_area_size是固定时,可以降低cluster size来提高fan-out。

从这里我们可以看出,提高hash_multiblock_io_count参数的值并不一定提高hash join的性能。

当驱动结果集生成的hash表全部可以放入PGA的hash area时,称为optimal,过程如下

这个是最优的hash join,它的成本就是两张标的full table scan,再加上微量的hash运算

如果进程的pga很小,或者驱动表结果集很大,超过了hash_area的大小,会用到临时表空间。
数据是经过两次hash运算的,先确定你的partition,再确定你的bulket,假设hash area小于整个hash table,但至少大于一个partition的size,这个时候走的就是onepass。
当我们生成好hash表后,状况是部分partition留在内存中,其他的partition留在磁盘临时表空间中,当然也有可能某个partition一半在内存,一半在磁盘,剩下的步骤大致如下:

相比optimal,他多出的成本是对于无法放入内存的partition,重新读取了一次,所以称为onepass,只要你的内存保证能装下一个partition,oracle都会腾挪空间,每个磁盘partition做到onepass

这是最复杂,最糟糕的hash join,此时hash area小到连一个partition也容纳不下,当扫描好驱动表后,可能只有半个partition留在hash area中,另半个加其他的partition全在磁盘上,剩下的步骤和onepass比价类似,不同的是针对partition的处理

由于驱动表只有半个partition在内存中,探测表对应的partition数据做探测时,如果匹配不上,这行还不能直接丢弃,需要继续保留到磁盘,和驱动表剩下的半个partition再做join,这里举例的是内存可以装下半个partition,如果装的更少的话,反复join的次数将更多,当发生multipass时,partition物理读的次数会显著增加

Mysql 优化器内部JOIN算法hash join Nestloopjoin及classic hash join CHJ过程详解

Mysql hash join之classic hash join CHJ过程详解

hash join的历史

优化器里的hash join算法在SQL Server、Oracle、postgress等数据库早已实现,而Mysql在8.0.18之后才支持。在8.0.18之前mysql只支持嵌套循环关联(nested loop join),这其中最简单就是简易嵌套循环关联simple nestloop join,随后mysql做了改进进而支持block nestloop join, index nestloop join and batched key access等算法,这也是hash join算法被推迟实现的部分原因。

hash join的概述

提到hash join之前自然得说Nestloopjoin,以两个表的关联为例,它其实是个双层循环,先遍历外层的表(n条),再拿每次对应的值去匹配、循环遍历内部的表(M条)。这样显然会有M*n的计算复杂度。如果能将外部表先装载到内存,然后再做内部表的匹配、遍历,计算的复杂度就会大大降低,这就是hash join的思想。

hash join的类型

hash join 从具体实现上又分为:

1经典hash join(In-Memory Join或classic hash join或CHJ)

2 磁盘分区hash join (On-Disk Hash Join)

3 Grace Hash Join

4 混合hash join(hybrid hash join)

hash join应用场景

hash join主要应用在以下条件:

1两个或多个表至少包含一个等值条件关联时。

2 关联的字段上没有索引。

3 关联条件可以是原始字段或者表达式(如 T1.col1+T1.col2 = T2.col3+T2.col4).

4 关联条件等号的两表只能出现一张表。

5 补充,可能使用到hash join的SQL写法示例:

 CREATE TABLE t1 (t1_1 INT, t1_2 INT);
 CREATE TABLE t2 (t2_1 INT, t2_2 INT)
 SELECT * FROM t1 JOIN t2 ON (t1.t1_1 = t2.t2_1);
 SELECT * FROM t1 JOIN t2 ON (t1.t1_1 = t2.t2_1 AND t1.t1_2 = t2.t2_2);
 SELECT * FROM t1 JOIN t2 ON (t1.t1_1 = t2.t2_1 AND t2.t2_2 > 43);
 SELECT * FROM t1 JOIN t2 ON (t1.t1_1 + t1.t1_2 = t2.t2_1);
 SELECT * FROM t1 JOIN t2 ON (FLOOR(t1.t1_1 + t1.t1_2) = CEIL(t2.t2_1 = t2.t2_2));

In-Memory Join classic hash join

经典hash join(classic hash join)简称CHJ,该算法由两部分构成,一是构建哈希表(hash table)过程,二是探测、匹配(probe)过程。

以有如下查询和表格结构为例:

CREATE TABLE t1 (foo INT);

CREATE TABLE t2 (bar INT);

insert into t1 values(12);

insert into t2 values(34);

… ...  -- 插入数据省略

SELECT * FROM t1 JOIN t2 on (t1.foo = t2.bar);

构建哈希表:

1 当两个表以含等值条件方式关联时其中一个表会被指定为构建表,该表会以哈希表(哈希的值来自于等值条件里的字段)的形式读入内存。

2 假设这里t1表被指定为构建表,那么将会通过哈希函数产生哈希表,这里t1的foo字段是是哈希函数里的键。

探测阶段:

此时join里的另外个表即t2作为探测表,在构建完成后,开始从t2探测表作为输入。这时以t2的bar (t1、t2两表关联时t2的字段)作为哈希的键,同时该键是用来匹配内存里的t1生成的哈希表。一旦匹配到记录则意味着找到目标,这个按照每一行匹配的过程就做探测过程。

In-Memory Join过程示意

构建阶段示例

探测阶段示例​​​​

执行计划查看In-Memory Join

EXPLAIN ANALYZE
SELECT CountryCode, country.Name AS Country,
city.Name AS City, city.District
FROM world.country IGNORE INDEX (Primary)
INNER JOIN world.city IGNORE INDEX (CountryCode)
ON city.CountryCode = country.Code
WHERE Continent = 'Asia';

-- 数据库来自mysql官网示例数据库world
-- 结果
EXPLAIN
-> Inner hash join (world.city.CountryCode = world.country.`Code`)  (cost=13870.82 rows=595) (actual time=58.807..134.662 rows=1766 loops=1)
    -> Table scan on city  (cost=0.80 rows=4046) (actual time=30.193..95.386 rows=4079 loops=1)
    -> Hash
        -> Filter: (world.country.Continent = 'Asia')  (cost=30.90 rows=34) (actual time=21.446..28.489 rows=51 loops=1)
            -> Table scan on country  (cost=30.90 rows=239) (actual time=21.435..28.338 rows=239 loops=1)

执行示例

 过程介绍

Step1:Country的Code字段会被哈希函数哈希并保存在关联缓存(即哈希表)内(join buffer)。

Step2:然后通过和step1里一样的哈希函数哈希的字段CountryCode进行表扫描(table scan)遍历city表里的每一行。

以上是关于Hash join算法的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 优化器内部JOIN算法hash join On-Disk Hash Join Grace Hash Join Hybrid hash join过程详解

Mysql 优化器内部JOIN算法hash join Nestloopjoin及classic hash join CHJ过程详解

mysql 8 中的hash join

Greenplum 架构详解 & Hash Join 算法介绍

深入理解Oracle表:三大表连接方式详解之Hash Join的定义,原理,算法,成本,模式和位图...

数据库多表连接方式介绍-HASH-JOIN