hybrid hash join
hybrid hash join是基于grace hash join 的优化。
在postgresql中的grace hash join 是这样做的:inner table太大不能一次性全部放到内存中,pg会把inner table 和outer table按照join的key分成多个分区,每个分区(有一个inner table子部分也有一个outer table的子部分)保存在disk上。再对每个分区用普通的hash join。每个分区称为一个batch,通过join key计算出hash value,然后计算出对应的batchNo与BucketNo:计算公式如下:
bucketNo = hash value % nbuckets;
batchNo = (hash value / nbuckets) % nbatch;
//nbuckets为buckets的个数,nbacth为batch的个数。
大致上和mysql差不多,不过mysql并没有分buckets。
判断是否需要多个batch的逻辑如下:
若 inner table的size + buckets的开销 < work_mem,使用单个batch。否则使用多个batch:
plan_rows:预估的inner table的行数
plan_width:预估的inner table的列数
NTUP_PER_BUCKET:单个buckets的tuple数据
Work_mem:为hashjoin分配的内存配额
hybrid hash join的优化在于:对于第一个batch不必写入disk,从而避免第一个batch的磁盘IO
具体过程如下:
1、首先对inner table进行分区/分batch,计算batchNo:
如果该tuple属于batch0,则加入内存中的hashtable中;
否则写入batchNo对应的disk file中。
总结就是batch0不用写如磁盘(当然也有例外,在下文会提到)
2、对outer table进行分区/分batch,计算batchNo:
如果tuple属于batch0,那么用key去内存hashtable寻找(equal_range or find),匹配则输出,否则继续读下一行probe tuple。
否则写入batchNo对应的disk file中。
3、outer table扫描完毕,batch0也处理完了。
开始按照No处理下一个batchx:
加载batchx的inner table到内存,build hash table
扫描batchx的outer table,进行probe。
batchx处理完,处理batchx+1,直到所有batch都处理完毕。
现在还有一个问题:如果分割后的batch0仍然太大,不能一次性放到内存中,怎么办?
postgresql的做法是将batch个数翻倍,从原本的n变为2n。重新扫描batch0的tuples,根据nbatch = 2n,重新计算所属的batch。如果重新计算后的batcth仍然属于batch0,就保留在内存中,否则从内存中拿出,写入到tuple对应的新batch中。
(此时batch0的后半部分数据被分配到batchn上)
注意,此时不会移动磁盘中batch file中已有的tuple,当处理到该batch的时候会处理。
还记得上文提到的hybrid hash join的取模操作吗?这个操作保证了,batch数目翻倍后,tuple所属的batch只会向后扩展。
刚刚说的只是batch0,当我们继续处理batch_i的时候,可能还是会遇到这个问题。那么就继续将nbatch数目翻倍吧!
当然tuple所属的batchNo也会变化。