1.概述

  hash join是一种数据库在进行多表连接时的处理算法,对于多表连接还有两种比较常用的方式:sort merge-join 和 nested loop。 为了比较清楚的介绍hash join的使用场景以及为何要引入这样一种连接算法,这里也会顺带简单介绍一下上面提到的两种join方式。

  连接方式是一个什么样的概念,或者说我们为何要有而且有好几种,对于不太了解数据库的人来讲可能这些是开头的疑惑。简单来讲,我们将数据存在不同的表中,而不同的表有着它们自身的表结构,不同表之间可以是有关联的,大部分实际使用中,不会仅仅只需要一张表的信息,比如需要从一个班级表中找出杭州地区的学生,再用这个信息去检索成绩表中他们的数学成绩,如果没有多表连接,那只能手动将第一个表的信息查询出来作为第二个表的检索信息去查询最终的结果,可想而知这将会是多么繁琐。

  对于几个常见的数据库,像oracle,postgresql它们都是支持hash-join的,mysql并不支持。在这方面,oracle和pg都已经做的比较完善了,hash-join本身的实现并不是很复杂,但是它需要优化器的实现配合才能最大的发挥本身的优势,我觉得这才是最难的地方。

  多表连接的查询方式又分为以下几种:内连接,外连接和交叉连接。外连接又分为:左外连接,右外连接和全外连接。对于不同的查询方式,使用相同的join算法也会有不同的代价产生,这个是跟其实现方式紧密相关的,需要考虑不同的查询方式如何实现,对于具体使用哪一种连接方式是由优化器通过代价的衡量来决定的,后面会简单介绍一下几种连接方式代价的计算。 hashjoin其实还有很多需要考虑和实现的地方,比如数据倾斜严重如何处理、内存放不下怎木办,hash如何处理冲突等,这些并不是本文介绍的重点,不再详述,每个拿出来都可以再讲一篇了。

  nested loop join

  嵌套循环连接,是比较通用的连接方式,分为内外表,每扫描外表的一行数据都要在内表中查找与之相匹配的行,没有索引的复杂度是O(N*M),这样的复杂度对于大数据集是非常劣势的,一般来讲会通过索引来提升性能。 

  sort merge-join

  merge join需要首先对两个表按照关联的字段进行排序,分别从两个表中取出一行数据进行匹配,如果合适放入结果集;不匹配将较小的那行丢掉继续匹配另一个表的下一行,依次处理直到将两表的数据取完。merge join的很大一部分开销花在排序上,也是同等条件下差于hash join的一个主要原因。

2.原理和实现

  简单的对于两个表来讲,hash-join就算讲两表中的小表(称S)作为hash表,然后去扫描另一个表(称M)的每一行数据,用得出来的行数据根据连接条件去映射建立的hash表,hash表是放在内存中的,这样可以很快的得到对应的S表与M表相匹配的行。

  对于结果集很大的情况,merge-join需要对其排序效率并不会很高,而nested loop join是一种嵌套循环的查询方式无疑更不适合大数据集的连接,而hash-join正是为处理这种棘手的查询方式而生,尤其是对于一个大表和一个小表的情况,基本上只需要将大小表扫描一遍就可以得出最终的结果集。

  不过hash-join只适用于等值连接,对于>, <, <=, >=这样的查询连接还是需要nested loop这种通用的连接算法来处理。如果连接key本来就是有序的或者需要排序,那么可能用merge-join的代价会比hash-join更小,此时merge-join会更有优势。

  好了,废话说了不少了,来讲讲实现,拿一条简单的多表sql查询语句来举个栗子:select * from t1 join t2 on t1.c1 = t2.c1 where t1.c2 > t2.c2 and t1.c1 > 1。这样一条sql进入数据库系统中,它是如何被处理和解剖的呢?sql:鬼知道我都经历了些什么。。。

  1.背景知识

  1.第一步呢,它需要经历词法以及语法的解析,这部分的输出是一颗带有token结点的语法树。

    

  语法分析,顾名思义这部分只是语法层面的剖析,将一个string的sql语句处理成为一颗有着雏形结构的node tree,每个结点有它们自身的特殊标识,但是并没有分析和处理这个结点的具体含义和值。

  2. 第二步是语义分析和重写处理。

  重写的过程不同的数据库可能有不同的处理,有些可能是跟逻辑执行过程放在一起,有的则分开。

  

  这一步做完树的形状大体上是与语法分析树保持一致的,但是此时的结点都携带了一些具体的信息,以where后面的表达式为例,这颗中缀表达式每一个结点都有了自身的类型和特定的信息,并不关心值是什么,这步做完后进入改写过程,改写是一种逻辑优化方式,使得一些复杂的sql语句变得更简单或者更符合数据库的处理流程。

  3.优化器处理

  优化器的处理是比较复杂的,也是sql模块最难的地方,优化无止境,所以优化器没有最优只有更优。优化器需要考虑方方面面的因素既要做的通用型很强又要保证很强的优化能力和正确性。

  优化器最重要的作用莫过于路径选择了,对于多表连接如何确定表连接的顺序和连接方式,不同的数据库有着不同的处理方式,pg支持动态规划算法,表数量过多的时候使用遗传算法。路径的确定又依赖于代价模型的实现,代价模型会维护一些统计信息,像列的最大值、最小值、NDV和DISTINCT值等,通过这些信息可以计算选择率从而进一步计算代价。

  

  回归到正文,使用哪一种连接方式就是在这里决定的,hash join 对大小表是有要求的,所以这里形成的计划是t1-t2还是t2-t1是不一样的,每种连接方式有着自身的代价计算方式。

  hash join的代价估算:

  COST = BUILD_COST + M_SCAN_COST + JOIN_CONDITION_COST + FILTER_COST

  简单来看,hash join的代价主要在于建立hash表、扫描M表、join条件连接和filter过滤,对于S表和M表都是只需要扫描一次即可,filter过滤是指t1.c2>t2.c2这样的条件过滤,对于t1.c1>1这样只涉及单表的条件会被下压,在做连接之前就被过滤了。

  优化器处理过后,会生成一颗执行计划树,真正的实现过程根据执行计划的流程操作数据,由低向上地递归处理并返回数据。

    

  2.hash join的实现

  hash join的实现分为build table也就是被用来建立hash map的小表和probe table,首先依次读取小表的数据,对于每一行数据根据连接条件生成一个hash map中的一个元組,数据缓存在内存中,如果内存放不下需要dump到外存。依次扫描探测表拿到每一行数据根据join condition生成hash key映射hash map中对应的元組,元組对应的行和探测表的这一行有着同样的hash key, 这时并不能确定这两行就是满足条件的数据,需要再次过一遍join condition和filter,满足条件的数据集返回需要的投影列。

      

  hash join实现的几个细节

  1.hash join本身的实现不要去判断哪个是小表,优化器生成执行计划时就已经确定了表的连接顺序,以左表为小表建立hash table,那对应的代价模型就会以左表作为小表来得出代价,这样根据代价生成的路径就是符合实现要求的。

  2.hash table的大小、需要分配多少个桶这个是需要在一开始就做好的,那分配多少是一个问题,分配太大会造成内存浪费,分配太小会导致桶数过小开链过长性能变差,一旦超过这里的内存限制,会考虑dump到外存,不同数据库有它们自身的实现方式。

  3.如何对数据hash,不同数据库有着自己的方式,不同的哈希方法也会对性能造成一定的影响。

3.pg和oracle实测

1.oracle

表结构

SQL> select * from or1;

     A        B
---------- ----------
1 1
2 2
3 3 SQL> select * from or2; A B C
---------- ---------- --------------------
1 2 a
2 3 b
4 5 d

内连接

SQL> select * from or1 join or2 on or1.a = or2.a;

     A        B           A      B C
---------- ---------- ---------- ---------- --------------------
1 1 1 2 a
2 2 2 3 b
SQL> explain plan for select * from or1 join or2 on or1.a = or2.a; Explained. SQL> SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE')); PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 738363828 ---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 192 | 4 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 3 | 192 | 4 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| OR1 | 3 | 78 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| OR2 | 3 | 114 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------- PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
--------------------------------------------------- 1 - access("OR1"."A"="OR2"."A") Note
-----
- dynamic statistics used: dynamic sampling (level=2) 19 rows selected.

左外连接,右外连接

SQL> select * from or1 left join or2 on or1.a = or2.a;

     A        B           A      B C
---------- ---------- ---------- ---------- --------------------
1 1 1 2 a
2 2 2 3 b
3 3 SQL> explain plan for select * from or1 left join or2 on or1.a = or2.a; Explained. SQL> SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE')); PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1456425992 ---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 192 | 4 (0)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 3 | 192 | 4 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| OR1 | 3 | 78 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| OR2 | 3 | 114 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------- PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
--------------------------------------------------- 1 - access("OR1"."A"="OR2"."A"(+)) Note
-----
- dynamic statistics used: dynamic sampling (level=2) 19 rows selected.

全外连接

SQL> select * from or1 full join or2 on or1.a = or2.a;

     A        B           A      B C
---------- ---------- ---------- ---------- --------------------
1 1 1 2 a
2 2 2 3 b
4 5 d
3 3 SQL> SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE')); PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2943417606 --------------------------------------------------------------------------------
-- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
| --------------------------------------------------------------------------------
-- PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 192 | 4 (0)| 00:00:01
| | 1 | VIEW | VW_FOJ_0 | 3 | 192 | 4 (0)| 00:00:01
| |* 2 | HASH JOIN FULL OUTER| | 3 | 192 | 4 (0)| 00:00:01
| | 3 | TABLE ACCESS FULL | OR1 | 3 | 78 | 2 (0)| 00:00:01
| PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------- | 4 | TABLE ACCESS FULL | OR2 | 3 | 114 | 2 (0)| 00:00:01
| --------------------------------------------------------------------------------
-- Predicate Information (identified by operation id):
--------------------------------------------------- PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
2 - access("OR1"."A"="OR2"."A") Note
-----
- dynamic statistics used: dynamic sampling (level=2) 20 rows selected.

2.postgresql

表数据量

hudson=# select count(*) from hj1;
count
--------
200022
(1 row)

hudson=# select count(*) from hj2;
count
--------
200000
(1 row)

内连接

hudson=# explain select * from hj1 join hj2 on hj1.a=hj2.a;
QUERY PLAN
------------------------------------------------------------------------
Hash Join (cost=7338.00..26467.34 rows=430732 width=51)
Hash Cond: (hj1.a = hj2.a)
-> Seq Scan on hj1 (cost=0.00..3467.22 rows=200022 width=25)
-> Hash (cost=3470.00..3470.00 rows=200000 width=26)
-> Seq Scan on hj2 (cost=0.00..3470.00 rows=200000 width=26)
(5 rows)

外连接

外连接使用大小表数据量差别比较大的两个表,可以清晰的看出hash join是以小表作为hash table的。

hudson=# explain select * from hj1 full join hj3 on hj1.a=hj3.a;
QUERY PLAN
------------------------------------------------------------------------
Hash Full Join (cost=7335.50..8905.67 rows=200022 width=33)
Hash Cond: (hj3.a = hj1.a)
-> Seq Scan on hj3 (cost=0.00..32.60 rows=2260 width=8)
-> Hash (cost=3467.22..3467.22 rows=200022 width=25)
-> Seq Scan on hj1 (cost=0.00..3467.22 rows=200022 width=25)
(5 rows)

hudson=# explain select * from hj1 left join hj3 on hj1.a=hj3.a;
QUERY PLAN
------------------------------------------------------------------------
Hash Right Join (cost=7335.50..8905.67 rows=200022 width=33)
Hash Cond: (hj3.a = hj1.a)
-> Seq Scan on hj3 (cost=0.00..32.60 rows=2260 width=8)
-> Hash (cost=3467.22..3467.22 rows=200022 width=25)
-> Seq Scan on hj1 (cost=0.00..3467.22 rows=200022 width=25)
(5 rows)

hudson=# explain select * from hj1 right join hj3 on hj1.a=hj3.a;
QUERY PLAN
------------------------------------------------------------------------
Hash Left Join (cost=7335.50..8905.67 rows=5270 width=33)
Hash Cond: (hj3.a = hj1.a)
-> Seq Scan on hj3 (cost=0.00..32.60 rows=2260 width=8)
-> Hash (cost=3467.22..3467.22 rows=200022 width=25)
-> Seq Scan on hj1 (cost=0.00..3467.22 rows=200022 width=25)
(5 rows)

可以看出,left join为了能够以小表建立hash table被转换为了right join, 同样right join也被改写成left join。

  

数据库多表连接方式介绍-HASH-JOIN的更多相关文章

  1. Oracle 表的连接方式(2)-----HASH JOIN的基本机制2

    Hash算法原理 对于什么是Hash算法原理?这个问题有点难度,不是很好说清楚,来做一个比喻吧:我们有很多的小猪,每个的体重都不一样,假设体重分布比较平均(我们考虑到公斤级别),我们按照体重来分,划分 ...

  2. Oracle 表的连接方式(2)-----HASH JOIN的基本机制3

    HASH JOIN的模式 hash join有三种工作模式,分别是optimal模式,onepass模式和multipass模式,分别在v$sysstat里面有对应的统计信息: SQL> sel ...

  3. Oracle 表的连接方式(2)-----HASH JOIN的基本机制1

    我们对hash join的常见误解,一般包括两个: 第一个误解:是我们经常以为hash join需要对两个做join的表都做全表扫描 第二个误解:是经常以为hash join会选择比较小的表做buil ...

  4. sql数据库的表连接方式图文详解

    sql数据库表连接,主要分为:内连接.外连接(左连接.右连接 .全连接).交叉连接,今天统一整合一下,看看他们的区别.   首先建表填充值. 学生表:student(id,姓名,年龄,性别 ) 成绩表 ...

  5. 数据库基础知识详解三:MVCC、范式以及表连接方式

    写在文章前:本系列文章用于博主自己归纳复习一些基础知识,同时也分享给可能需要的人,因为水平有限,肯定存在诸多不足以及技术性错误,请大佬们及时指正. 8.MVCC 多版本并发控制(Multi-Versi ...

  6. Oracle 表连接方式分析 .

    一 引言 数据仓库技术是目前已知的比较成熟和被广泛采用的解决方案,用于整和电信运营企业内部所有分散的原始业务数据,并通过便捷有效的数据访问手段,可以支持企业内部不同部门,不同需求,不同层次的用户随时获 ...

  7. 看懂Oracle执行计划、表连接方式

    看懂Oracle执行计划  原文:https://www.cnblogs.com/Dreamer-1/p/6076440.html 最近一直在跟Oracle打交道,从最初的一脸懵逼到现在的略有所知,也 ...

  8. Oracle的表连接方式

    Oracle的表连接方式: 1.Nl Join连接(嵌套连接) 2.Hash Join(哈希连接) 3.Merge Sort Join(排序合并连接) 各种连接的使用场景: 1. 排序合并连接是偏向于 ...

  9. Oracle多种表连接方式

    1. 内连接(自然连接) 2. 外连接 (1)左外连接 (左边的表不加限制) (2)右外连接(右边的表不加限制) (3)全外连接(左右两表都不加限制) 3. 自连接(同一张表内的连接) SQL的标准语 ...

随机推荐

  1. sql要点

    oracle 并操作 union  自动去除重复    如果想保留重复,需要使用union all 交操作intersect  自动去除重复    如果想保留重复,需要使用intersect all ...

  2. Css背景渐变

    语法: background:linear-gradient( 渐变方向,起点颜色,终点颜色 ) 参数说明: 渐变方向:可以使用top,left,或者指定具体的角度(deg为单位),比如top是自上而 ...

  3. C#如何实现下载文件保存到本地上面去

    public void btnTemplate_Click(object sender, EventArgs e) { string strResult = string.Empty; string ...

  4. 基数排序 java 实现

    基数排序 java 实现 Wikipedia: Radix sort geeksforgeeks: Radix sort 数学之美番外篇:快排为什么那样快 Java排序算法总结(八):基数排序 排序八 ...

  5. [转] 经典SQL练习题

    原题目来自qaz13177_58_CSDN博客 http://blog.csdn.net/qaz13177_58_/article/details/5575711/#sql 只是更新个人答案供参考 表 ...

  6. js 控制Div循环显示 非插件版

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. mysql binlog日志查看及解码

    mysql bin log日志导出 mysqlbinlog  mysql-bin.000005 > /home/17bin.log 需要添加参数(--base64-output=decode-r ...

  8. Java数据结构与算法之---求两个数的最大公约数(欧几里得算法)

    一个简单的小算法来获取两个数的最大公约数, public class Test { public static void main(String[] args) { long result = gcd ...

  9. 马士兵Java视频教程 —— 学习顺序

    第一部分:J2se学习视频内容包括: 尚学堂科技_马士兵_JAVA视频教程_JDK5.0_下载-安装-配置 尚学堂科技_马士兵_JAVA视频教程_J2SE_5.0_第01章_JAVA简介_源代码_及重 ...

  10. MVC Razor模板引擎 @RenderBody、@RenderPage、@RenderSection及Html.RenderPartial、Html.RenderAction

    一.Views文件夹 -> Shared文件夹下的 _Layout.cshtml 母版页 @RenderBody 当创建基于_Layout.cshtml布局页面的视图时,视图的内容会和布局页面合 ...