oracle表连接深入浅出(二)

2014-11-24 09:44:03 · 作者: · 浏览: 1
count , hash_multiblock_io_count 在 oracle9i 中是隐含参数。这里需要注意的是 fan-out 并不是 build input 的大小 /hash_ara_size ,也就是说 oracle 决定的分区大小有可能还是不能完全存放在 hash area 内存中。大的 fan-out 导致许多小的分区,影响性能,而小的 fan-out 导致少数的大的分区,以至于每个分区不能全部存放在内存中,这也影响 hash join 的性能。

Oracle 采用内部一个 hash 函数作用于连接键上,将 S 和 B 分割成多个分区,在这里我们假设这个 hash 函数为求余函数,即 Mod(join_column_value,10) 。这样产生十个分区,如下表。


分区 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9
0,0,10,10 1,1,1,1,11 2,2,2,2,2,2 3 NULL NULL NULL NULL 8 9,9,9
S0 10
S1 1,1,1
S2 Null
S3 3,3
S4 4,4,4,4
S5 5
S6 NULL
S7 NULL
S8 8,8,8,8
S9 NULL


经过这样的分区之后,只需要相应的分区之间做 join 即可(也就是所谓的 partition pairs ),如果有一个分区为 NULL 的话,则相应的分区 join 即可忽略。

在将 S 表读入内存分区时,oracle将记录连接键的唯一值,构建成所谓的位图向量,它需要占 hash area 内存的 5% 左右。在这里即为 {1,3,4,5,8,10} 。

当对 B 表进行分区时,将每一个连接键上的值与位图向量相比较,如果不在其中,则将其记录丢弃。在我们这个例子中, B 表中以下数据将被丢弃 {0,0,2,2,2,2,2,2,9,9,9,9,9} 。这个过程就是位图向量过滤。

当 S1,B1 做完连接后,接着对 Si,Bi 进行连接,这里 oracle 将比较两个分区,选取小的那个做 build input ,就是动态角色互换,这个动态角色互换发生在除第一对分区以外的分区上面。

Hash Join 算法
第 1 步:判定小表是否能够全部存放在 hash area 内存中,如果可以,则做内存 hash join 。如果不行,转第二步。
第 2 步:决定 fan-out 数。
(Number of Partitions) * C<= Favm *M
其中 C 为 Cluster size ,
其值为 DB_BLOCK_SIZE*HASH_MULTIBLOCK_IO_COUNT ; Favm 为 hash area 内存可以使用的百分比,一般为 0.8 左右; M 为 Hash_area_size 的大小。

第 3 步:读取部分小表 S ,采用内部 hash 函数 ( 这里称为 hash_fun_1) ,将连接键值映射至某个分区,同时采用 hash_fun_2 函数对连接键值产生另外一个 hash 值,这个 hash 值用于创建 hash table 用,并且与连接键值存放在一起。

第 4 步:对 build input 建立位图向量。
第 5 步:如果内存中没有空间了,则将分区写至磁盘上。
第 6 步:读取小表 S 的剩余部分,重复第三步,直至小表 S 全部读完。
第 7 步:将分区按大小排序,选取几个分区建立 hash table( 这里选取分区的原则是使选取的数据最多 ) 。
第 8 步:根据前面用 hash_fun_2 函数计算好的 hash 值,建立 hash table 。
第 9 步:读取表 B ,采用位图向量进行位图向量过滤。
第 10 步:对通过过滤的数据采用 hash_fun_1 函数将数据映射到相应的分区中去,并计算 hash_fun_2 的 hash 值。
第 11 步:如果所落的分区在内存中,则将前面通过 hash_fun_2 函数计算所得的 hash 值与内存中已存在的 hash table 做连接, 将结果写致磁盘上。 如果所落的分区不在内存中,则将相应的值与表 S 相应的分区放在一起。

第 12 步:继续读取表 B ,重复第 9 步,直至表 B 读取完毕。
第 13 步:读取相应的 (Si,Bi) 做 hash 连接。在这里会发生动态角色互换。
第 14 步:如果分区过后,最小的分区也比内存大,则发生 nested- loop hash join 。

Hash Join 的成本
1. In-Memory 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)

2. On-Disk Hash Join
根据上述的步骤描述,我们可以看出
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 的性能。

当连接的两个表是用等值连接并且表的数据量比较大时,优化器才可能采用哈希连接。哈希连接是基于CBO的。只有在 数据库
初始化参数HASH_JOIN_ENABLED设为True,并且为参数PGA_AGGREGATE_TARGET设置了一个足够大的值的时候, Oracle才会使用哈
希边连接。HASH_AREA_SIZE是向下兼容的参数,但在Oracle9i之前的版本中应当使用HASH_AREA_SIZE。当使用ORDERED提示时,
FROM子句中的第一张表将用于建立哈希表。

当缺少有用的索引时,哈希连接比嵌套循环连接更加有效。哈希连接也可能比嵌套循环连接更快,因为处理内存中的哈希表
比检索B_树索引更加迅速。 Hash Join 适合于小表与大表连接、