哈希连接(HASH JOIN)详细执行步骤如下所示:
1.Oracle根据HASH_AREA_SIZE、DB_BLOCK_SIZE和_HASH_MUTBLOCK_IO_COUNT的值来决定HASH Partition的数量(HASH PARTITION是将结果集分为若干个分区,每个分区有相同数量的bucket,bucket是hash映射的单位,而partition则是I/O单位)。Oracle会保留hash area的20%来存储分区的头信息、hash位图信息和hash表。因此,这个数字的计算公式是:
Partition=0.8*hash_area_size/(hash_multiblock_io_count*db_block_size)
各参数含义后边解释。
2.在表1和表2施加指定的谓词条件之后,结果集较小的那个作为驱动结果集,记为S,结果集较大的那个是被驱动结果集,记为B。
3.oracle遍历S,一条条读取记录,并对连接列的值通过两个哈希算法进行哈希运算,得到两个值hash1和hash2。然后根据hash1的值将S中对应的记录放到对应的hash partition的hash bucket之中,同时存储进去的还有hash2的值。如果在读取过程中hash area已经满了,那么oracle会选择记录量最大的hash partition写入到磁盘中(临时表空间),如果之后再有需要放到该partition的值,那么就会更新磁盘,如果频繁更新磁盘会导致性能问题。在构建hash partition时会同时构建一个BITMAP,记录该partition当中每个bucket是否为空。遍历完后oracle会根据每个partition当中的记录数做一个排序,将记录数少的partition优先放到内存中,以保证尽量多的partition保留在内存中。
4.之后开始遍历B,也针对每一条记录使用两个哈希算法计算出两个值hash1和hash2。根据计算出来的hash1去寻找匹配的hash partition以及hash bucket,然后对hash bucket当中的每条记录进行匹配,校验连接列数据是否真的相等。如果BITMAP显示对应的hash bucket中有数据却在内存中找不到,说明该hash bucket已经刷新到磁盘当中。这时候会再建立一个hash partition记录这个数据,刷新到磁盘中,如果没有对应的hash bucket则说明B中的该记录在S中不存在匹配的数据,则不需要再进行保留。
5.之上的顺序做完之后能保证在内存中的数据已经进行了匹配,剩下的就是S和B存储在磁盘上的记录。此时因为已经通过计算完的hash1进行了partition和bucket的分配,剩下的就是对相对应的S 的partition和 B的partition进行匹配,这个匹配过程中会使用hash2的值再构建hash table,用于分开数据记录,以方便进行匹配。
6.最后所有匹配成功的记录就是哈希连接后所对应的结果集。
下面是一开始的几个参数的介绍:
1.HASH_AREA_SIZE
这个参数控制每个会话的hash内存空间有多大。它也可以在会话级和实例级被修改。默认(也是推荐)值是sort area空间大小的两倍
(2*SORT_AREA_SIZE)。要提高hash join的效率,就一定尽量保证sort area足够大,能容纳下整个小表的数据。但是因为每个会话都会
开辟一个这么大的内存空间作为hash内存,所以不能过大(一般不建议超过2M)。
在Oracle9i及以后版本中,Oracle不推荐在dedicated server中使用这个参数来设置hash内存,而是推荐通过设置
PGA_AGGRATE_TARGET参数来自动管理PGA内存。保留HASH_AREA_SIZE只是为了向后兼容。在dedicated server中,hash area是从
PGA中分配的,而在MTS(Multi-Threaded Server)中,hash area是从UGA中分配的。
另外,还要注意的是,每个会话并不一定只打开一个hash area,因为一个查询中可能不止一个hash join,这是就会相应同时打开多个
hash area。
2.HAHS_MULTIBLOCK_IO_COUNT
这个参数决定每次读入hash area的数据块数量。因此它会对IO性能产生影响。他只能在init.ora或spfile中修改。在8.0及之前版本,
它的默认值是1,在8i及以后版本,默认值是0。一般设置为1-(65536/DB_BLOCK_SIZE)。
在9i中,这个参数是一个隐藏参数:_HASH_MULTIBLOCK_IO_COUNT,可以通过表x$ksppi查询和修改。
另外,在MTS中,这个参数将不起作用(只会使用1)。
它的最大值受到OS的IO带宽和DB_BLOCK_SIZE的影响。既不能大于MAX_IO_SIZE/DB_BLOCK_SIZE。
在8i及以后版本,如果这个值设置为0,则表示在每次查询时,Oracle自己自动计算这个值。这个值对IO性能影响非常大,因此,建议不要
修改这个参数,使用默认值0,让Oracle自己去计算这个值。
如果一定要设置这个值,要保证以下不等式能成立:
R/M < Po2(M/C)
其中,R表示小表的大小;M=HASH_AREA_SIZE*0.9;Po2(n)为n的2次方;C=HASH_MULTIBLOCK_IO_COUNT*DB_BLOCK_SIZE。
最后附上从网上看到的一个对hash join的优化案例,感觉很不错:
http://www.itpub.net/forum.php?mod=viewthread&tid=1191228