散列连接(HASH JOIN)
(2012-12-04 21:35:02)
标签:
it |
如果没有索引可用,或者CBO认为索引连接不够有效, 那么就会采用散列(HASH)连接。
原理说明
为了便于说明, 先创建一个T2表:
SQL>drop table t2;
操作已执行
已用时间: 22.633(ms) clock tick:46339156. Execute id is 8.
SQL>create table t2 as select id, name from sysobjects;
操作已执行
已用时间: 4.962(ms) clock tick:10150025. Execute id is 9.
SQL>select count(*) from t2;
行号
---------- -------------------
1
已用时间: 0.939(ms) clock tick:1919537. Execute id is 10.
现在需要与前面创建的T1做连接, 连接条件为T1.ID = T2.ID, 用HASH连接怎么处理呢?方法如下:
1.
2.
3.
4.
5.
6.
7.
8.
最后我们获得的结果集就是连接的结果。为什么第7步需要检查呢?因为不同的值可能有相同的HASH值,而相同值必定在同一个桶中。HASH连接分为两个阶段,其中1-5是构造HASH表阶段,6-8是扫描匹配阶段。
可配置的内存参数
SQL>select * from v$dm_ini
where para_name in ('JOIN_HASH_SIZE', 'HJ_BUF_GLOBAL_SIZE', 'HJ_BUF_SIZE', 'HJ_BLK_SIZE');
行号
---------- ------------------ ----------
1
2
3
4
HJ_BUF_GLOBAL_SIZE缺省为500M, 表示系统允许同时进行的HASH连接最多使用500M内存,如果系统需要同时做很多个HASH连接,可以考虑增大这个参数;它只是个上限,增大它并不会实际分配这么多的内存。
JOIN_HASH_SIZE缺省为50万,用来确定HASH表的尺寸;值越大,则冲突的可能性越小,但是初始化HASH也是有代价的,如果系统中的HASH连接左表很小的话,减小这个值可以获得更好的性能;反之,如果左表很大,则为了减少冲突,可以增大它;
HJ_BLK_SIZE缺省为1M, 表示扫描左表,构造HASH的过程中,每次申请内存的数量;
HJ_BUF_SIZE缺省为50M, 表示单个HASH连接用于构造HASH表的最大内存使用量;对于类似TPC-H性能测试,或者大量使用HASH连接的应用,需要增大这个值。修改这个值并不会立即分配这么多内存, 也只是一个上限。
代价计算
其中, BUILD_HASH_BASE_COST为一个可配置的调节参数,缺省为0, LEFT_COST, RIGHT_COST分别表示获取左、右表的代价,BASE_HI_CPU, HI_CPU和HI_SEARCH_CPU也是可配置的参数,分别表示一次HASH连接的基本CPU时钟数,构造hash表时,平均处理一条记录的时钟周期数,以及在hash表中探测一次所需要的时钟周期;因此我们看到总的代价包括了左右表原来的代价, 加上构造HASH表的代价和探测代价。构造HASH表的代价与左表的记录数目成正比,而探测代价与右表的记录数目成正比,而且一般HI_CPU比HI_SEARCH_CPU要大,以确保同样条件下,使用小表做为左表。
下面是T1, T2连接的一个例子, 为了确保使用HASH连接,先确保T1(ID)上没有索引。虽然这个查询中,T1放在前面,但是CBO还是选择小表T2做了左表。
SQL>drop index t1l01;
drop index t1l01
第1 行附近出现错误[-2139]:索引[T1L01]不存在.
SQL>select count(*) from t1;
行号
---------- -------------------
1
已用时间: 0.791(ms) clock tick:1611850. Execute id is 31.
SQL>select count(*) from t2;
行号
---------- -------------------
1
已用时间: 0.438(ms) clock tick:891520. Execute id is 32.
SQL>explain select count(*) from t1, t2 where t1.id = t2.id;
#NSET2: [21, 1, 0]
已用时间: 0.928(ms) clock tick:1893860. Execute id is 0.
SQL>select count(*)
行号
---------- -------------------
1
已用时间: 19.468(ms) clock tick:39860176. Execute id is 33.
散列归并连接( Hash & Merge)
如果内存不足,或者在创建HASH表的阶段,申请的内存达到了HJ_BUF_SIZE设定的值,且左表还没有扫描结束时,就把当前的HASH连接自动转换为Hash-Merge连接。
这是一个比较复杂的过程。系统会创建一个NSORT对象A, 然后把当前HASH表的记录,附上桶号后,提交给对象A,然后继续扫描左表,重复这个过程, 直到把所有的记录都提交给A。 对右表也创建一个NSORT对象B, 重复同样的操作,直到把右表的记录都提交给了B。A, B两个NSORT对象会自动以(桶号, 连接KEY)作为组合键给数据排序,系统只需要对A,B两路数据作一个归并就可以获得连接结果。
NSORT是 DM7内部一个专么用于排序的模块,它支持利用临时表空间进行外排序。临时表空间的访问通过RECYCLE系统缓冲区,因此增大RECYCLE缓冲区的大小,可以减少甚至避免物理IO, 从而有效提升Hash-Merge的连接性能。RECYCLE的大小(单位为M)可以查询:
SQL>select * from v$dm_ini where para_name = 'RECYCLE';
行号
---------- --------- ----------
1
归并连接(Merge Join)
SQL>explain select t1.id, t3.id from t1, t3 where t1.id = t3.id;
#NSET2: [26, 32738, 0]
其代价计算的基本公式如下: