06 hash join (Oracle里的哈希连接原理)
hash join (Oracle里的哈希连接原理)
哈希连接(HASH JOIN)是一种两个表在做表连接时主要依靠哈希运算来得到连接结果集的表连接方法。
在Oracle 7.3之前,Oracle数据库中的常用表连接方法就只有排序合并连接和嵌套循环连接这两种,但这两种表连接方法都有其明显缺陷。对于排序合并连接,如果两个表在施加了目标SQL中指定的谓词条件(如果有的话)后得到的结果集很大且需要排序的话,则这种情况下的排序合并连接的执行效率一定是很差的;而对于嵌套循环连接,如果驱动表所对应的驱动结果集的记录数很大,即便在被驱动表的连接列上存在索引,此时使用嵌套循环连接的执行效率也同样会很差。
为了解决排序合并连接和嵌套循环连接在上述情形下执行效率不高的问题,同时也为了给优化器提供一种新的选择,Oracle在Oracle 7.3中引入了哈希连接。从理论上来说,哈希连接的执行效率会比排序合并连接和嵌套循环连接的执行效率要高,当然,实际情况并不总是这样。
在Oracle 10g及其以后的Oracle数据库版本中,优化器(实际上是CBO,因为哈希连接仅适用于CBO)在解析目标SQL时是否考虑哈希连接是受限于隐含参数_HASH_JOIN_ENABLED,而在Oracle 10g以前的Oracle数据库版本中,CBO在解析目标SQL时是否考虑哈希连接是受限于参数HASH_JOIN_ENABLED。
_HASH_JOIN_ENABLED的默认值是TRUE,表示允许CBO在解析目标SQL时考虑哈希连接。当然,即使你将该参数的值改成了FALSE,我们使用USE_HASH Hint依然可以让CBO在解析目标SQL时考虑哈希连接,这说明USE_HASH Hint的优先级高于参数_HASH_JOIN_ENABLED。
如果两个表(这里将它们分别命名为表T1和表T2)在做表连接时使用的是哈希连接,则Oracle在做哈希连接时会依次顺序执行如下步骤:
1、 首先Oracle会根据参数HASH_AREA_SIZE、DB_BLOCK_SIZE和_HASH_MULTIBLOCK_IO_COUNT的值来决定Hash Partition的数量(Hash Partition是一个逻辑上的概念,所有Hash Partition的集合就被称之为Hash Table,即一个Hash Table是由多个Hash Partition所组成,而一个Hash Partition又是由多个Hash Bucket所组成);
2、 表T1和T2在施加了目标SQL中指定的谓词条件(如果有的话)后得到的结果集中数据量较小的那个结果集会被Oracle选为哈希连接的驱动结果集,这里我们假设T1所对应的结果集的数据量相对较小,我们记为S;T2所对应的结果集的数据量相对较大,我们记为B;显然这里S是驱动结果集,B是被驱动结果集;
3、 接着Oracle会遍历S,读取S中的每一条记录,并对S中的每一条记录按照该记录在表T1中的连接列做哈希运算,这个哈希运算会使用两个内置哈希函数,这两个哈希函数会同时对该连接列计算哈希值,我们把这两个内置哈希函数分别记为hash_func_1和hash_func_2,它们所计算出来的哈希值分别记为hash_value_1和hash_value_2;
4、 然后Oracle会按照hash_value_1的值把相应的S中的对应记录存储在不同Hash Partition的不同Hash Bucket里,同时和该记录存储在一起的还有该记录用hash_func_2计算出来的hash_value_2的值。注意,存储在Hash Bucket里的记录并不是目标表的完整行记录,而是只需要存储位于目标SQL中的跟目标表相关的查询列和连接列就足够了;我们把S所对应的每一个Hash Partition记为Si;
5、 在构建Si的同时,Oracle会构建一个位图(BITMAP),这个位图用来标记Si所包含的每一个Hash Bucket是否有记录(即记录数是否大于0);
6、 如果S的数据量很大,那么在构建S所对应的Hash Table时,就可能会出现PGA的工作区(WORK AREA)被填满的情况,这时候Oracle会把工作区中现有的Hash Partition中包含记录数最多的Hash Partition写到磁盘上(TEMP表空间);接着Oracle会继续构建S所对应的Hash Table,在继续构建的过程中,如果工作区又满了,则Oracle会继续重复上述挑选包含记录数最多的Hash Partition并写回到磁盘上的动作;如果要构建的记录所对应的Hash Partition已经事先被Oracle写回到了磁盘上,则此时Oracle就会去磁盘上更新该Hash Partition,即会把该条记录和hash_value_2直接加到这个已经位于磁盘上的Hash Partition的相应Hash Bucket中;注意,极端情况下可能会出现只有某个Hash Partition的部分记录还在内存中,该Hash Partition的剩余部分和余下的所有Hash Partition都已经被写回到磁盘上;
7、 上述构建S所对应的Hash Table的过程会一直持续下去,直到遍历完S中的所有记录为止;
8、 接着,Oracle会对所有的Si按照它们所包含的记录数来排序,然后Oracle会把这些已经排好序的Hash Partition按顺序依次、并且尽可能的全部放到内存中(PGA的工作区),当然,如果实在放不下的话,放不下的那部分Hash Partition还是会位于磁盘上。我认为这个按照Si的记录数来排序的动作不是必须要做的,因为这个排序动作的根本目的就是为了尽可能多的把那些记录数较小的Hash Partition保留在内存中,而将那些已经被写回到磁盘上、记录数较大且现有内存已经放不下的Hash Partition保留在磁盘上,显然,如果所有的Si本来就都在内存中,也没发生过将Si写回到磁盘的操作,那这里根本就不需要排序了。
9、 至此Oracle已经处理完S,现在可以来开始处理B了;
10、 Oracle会遍历B,读取B中的每一条记录,并对B中的每一条记录按照该记录在表T2中的连接列做哈希运算,这个哈希运算和步骤3中的哈希运算是一模一样的,即这个哈希运算还是会用步骤3中的hash_func_1和hash_func_2,并且也会计算出两个哈希值hash_value_1和hash_value_2;接着Oracle会按照该记录所对应的哈希值hash_value_1去Si里找匹配的Hash Bucket;如果能找到匹配的Hash Bucket,则Oracle还会遍历该Hash Bucket中的每一条记录,并会校验存储于该Hash Bucket中的每一条记录的连接列,看是否是真的匹配(即这里要校验S和B中的匹配记录所对应的连接列是否真的相等,因为对于Hash运算而言,不同的值经过哈希运算后的结果可能是一样的),如果是真的匹配,则上述hash_value_1所对应B中的记录的位于目标SQL中的查询列和该Hash Bucket中的匹配记录便会组合起来,一起作为满足目标SQL连接条件的记录返回;如果找不到匹配的Hash Bucket,则Oracle就会去访问步骤5中构建的位图,如果位图显示该Hash Bucket在Si中对应的记录数大于0,则说明该Hash Bucket虽然不在内存中,但它已经被写回到了磁盘上,则此时Oracle就会按照上述hash_value_1的值把相应B中的对应记录也以Hash Partition的方式写回到磁盘上,同时和该记录存储在一起的还有该记录用hash_func_2计算出来的hash_value_2的值;如果位图显示该Hash Bucket在Si中对应的记录数等于0,则Oracle就不用把上述hash_value_1所对应B中的记录写回到磁盘上了,因为这条记录必然不满足目标SQL的连接条件;这个根据位图来决定是否将上述hash_value_1所对应B中的记录写回到磁盘的动作就是所谓的“位图过滤”;我们把B所对应的每一个Hash Partition记为Bj;
11、 上述去Si中查找匹配Hash Bucket和构建Bj的过程会一直持续下去,直到遍历完B中的所有记录为止;
12、 至此Oracle已经处理完所有位于内存中的Si和对应的Bj,现在只剩下位于磁盘上的Si和Bj还未处理;
13、 因为在构建Si和Bj时用的是同样的哈希函数hash_func_1和hash_func_2,所以Oracle在处理位于磁盘上的Si和Bj的时候可以放心的配对处理,即只有对应Hash Partition Number值相同的Si和Bj才可能会产生满足连接条件的记录;这里我们用Sn和Bn来表示位于磁盘上且对应Hash Partition Number值相同的Si和Bj;
14、 对于每一对儿Sn和Bn,它们之中记录数较少的会被当作驱动结果集,然后Oracle会用这个驱动结果集的Hash Bucket里记录的hash_value_2来构建新的Hash Table,另外一个记录数较大的会被当作被驱动结果集,然后Oracle会用这个被驱动结果集的Hash Bucket里记录的hash_value_2去上述构建的新Hash Table中找匹配记录;注意,对每一对儿Sn和Bn而言,Oracle始终会选择它们中记录数较少的来作为驱动结果集,所以每一对儿Sn和Bn的驱动结果集都可能会发生变化,这就是所谓的“动态角色互换”;
15、 步骤14中如果存在匹配记录,则该匹配记录也会作为满足目标SQL连接条件的记录返回;
16、 上述处理Sn和Bn的过程会一直持续下去,直到遍历完所有的Sn和Bn为止。
对于哈希连接的优缺点及适用场景,我们有如下总结:
Ÿ 哈希连接不一定会排序,或者说大多数情况下都不需要排序;
Ÿ 哈希连接的驱动表所对应的连接列的可选择性应尽可能的好,因为这个可选择性会影响对应Hash Bucket中的记录数,而Hash Bucket中的记录数又会直接影响从该Hash Bucket中查找匹配记录的效率;如果一个Hash Bucket里所包含的记录数过多,则可能会严重降低所对应哈希连接的执行效率,此时典型的表现就是该哈希连接执行了很长时间都没有结束,数据库所在database server上的CPU占用率很高,但目标SQL所消耗的逻辑读却很低,因为此时大部分时间都耗费在了遍历上述Hash Bucket里的所有记录上,而遍历Hash Bucket里记录这个动作是发生在PGA的工作区里,所以不耗费逻辑读;
Ÿ 哈希连接只适用于CBO、它也只能用于等值连接条件(即使是哈希反连接,Oracle实际上也是将其转换成了等价的等值连接);
Ÿ 哈希连接很适合于一个小表和大表之间的表连接,特别是在小表的连接列的可选择性非常好的情况下,这时候哈希连接的执行时间就可以近似看作是和全表扫描那个大表所耗费的时间相当;
Ÿ 当两个表做哈希连接时,如果这两个表在施加了目标SQL中指定的谓词条件(如果有的话)后得到的结果集中数据量较小的那个结果集所对应的Hash Table能够完全被容纳在内存中时(PGA的工作区),则此时的哈希连接的执行效率会非常高。
06 hash join (Oracle里的哈希连接原理)的更多相关文章
- Oracle 哈希连接原理
<基于Oracle的sql优化>里关于哈希连接的原理介绍如下: 哈希连接(HASH JOIN)是一种两个表在做表连接时主要依靠哈希运算来得到连接结果集的表连接方法. 在Oracle 7.3 ...
- oracle 表连接 - hash join 哈希连接
一. hash 连接(哈希连接)原理 指的是两个表连接时, 先利用两表中记录较少的表在内存中建立 hash 表, 然后扫描记录较多的表并探測 hash 表, 找出与 hash 表相匹配的行来得到结果集 ...
- Oracle 表的连接方式(2)-----HASH JOIN的基本机制2
Hash算法原理 对于什么是Hash算法原理?这个问题有点难度,不是很好说清楚,来做一个比喻吧:我们有很多的小猪,每个的体重都不一样,假设体重分布比较平均(我们考虑到公斤级别),我们按照体重来分,划分 ...
- oracle Hash Join及三种连接方式
在Oracle中,确定连接操作类型是执行计划生成的重要方面.各种连接操作类型代表着不同的连接操作算法,不同的连接操作类型也适应于不同的数据量和数据分布情况. 无论是Nest Loop Join(嵌套循 ...
- Oracle 三种连接方式 NESTED LOOP HASH JOIN SORT MERGE JOIN
NESTED LOOP: 对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择.在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表中检索找到与它匹配的行,因此整个查询返回的结果集不能太大( ...
- oracle多表连接方式Hash Join Nested Loop Join Merge Join
在查看sql执行计划时,我们会发现表的连接方式有多种,本文对表的连接方式进行介绍以便更好看懂执行计划和理解sql执行原理. 一.连接方式: 嵌套循环(Nested Loops (NL) ...
- Oracle性能优化之 Oracle里的优化器
优化器(optimizer)是oracle数据库内置的一个核心子系统.优化器的目的是按照一定的判断原则来得到它认为的目标SQL在当前的情形下的最高效的执行路径,也就是为了得到目标SQL的最佳执行计划. ...
- Nested loops、Hash join、Sort merge join(三种连接类型原理、使用要点)
nested loop 嵌套循环(原理):oracle从较小结果集(驱动表.也可以被称为outer)中读取一行,然后和较大结果集(被侦查表,也可以叫做inner)中的所有数据逐条进行比较(也是等值连接 ...
- NESTED LOOPS & HASH JOIN & SORT MERGE JOIN
表连接方式及使用场合 NESTED LOOP 对于被连接的数据子集较小的情况,nested loop连接是个较好的选择.nested loop就是扫描一个表,每读到一条记录,就根据索引去另一个表里面查 ...
随机推荐
- asp.net 日期转换为大写汉字
//年份转换为大写汉字 public static string numtoUpper(int num) { return "零壹贰叁肆伍陆柒捌玖"[num].ToString() ...
- Linux-学习笔记(PHP向)<一>
Linux常用命令 使用PHP服务器端脚本编程语言进行网站开发,需要在lamp环境下进行,Linux作为”四剑客”之一是有必要了解熟悉的,而Linux系统并不像windows操作系统那样,以图形化的界 ...
- 表单校验常用原生js库
1.字符串去除左右空格继承形式// 除去左右空格String.prototype.Trim = function() { return this.replace(/(^\s*)|(\s*$)/g, & ...
- cookie结合js 实现记住的拖拽
哈喽!!!我胡汉三又回来啦!!!有木有记挂挪啊!咱们今天说一个 cookie结合JS的小案例哦! 话不多说直接上代码: <!DOCTYPE html> <html> <h ...
- 关于Function原型对象和Object原型对象的一些疑惑
网上有一道美团外卖的面试题是这样的: Function.prototype.a = 'a'; Object.prototype.b = 'b'; function Person(){}; var p ...
- (C#) 表达式树
需求是将一个string 表达式 转换成 逻辑 表达式 并得到结果. 例如: bool result = (key1==val1) || (key2!=val2) && (key3== ...
- 浅谈回归(二)——Regression 之历史错误翻译
我很好奇这个问题,于是搜了一下.我发现 Regression 这个词 本意里有"衰退"的意思. 词根词缀: re- 回 , 向后 + -gress- 步 , 级 + -ion 名词 ...
- android之键盘转载
显示键盘: EditText editText.setFocusable(true); editText.setFocusableInTouchMode(true); editText.request ...
- M-PalindromeP-DP
Palindrome Partitioning 动态规划+深度优先搜索 https://leetcode.com/discuss/23480/c-solution-with-dp-and-dfs-12 ...
- Sqlserver2008+搜索型注入技术
简单的判断搜索型注入漏洞存在不存在的办法是先搜索',如果出错,说明90%存在这个漏洞.然后搜索%,如果正常返回,说明95%有洞了. 然后再搜索一个关键字,比如2006吧,正常返回所有2006相关的信息 ...