2. 路径生成
前面已经介绍了路径生成中使用的动态规划方法,并且介绍了在累积过程中如何生成当前层的RelOptInfo。对于生成当前层的RelOptInfo会面临几个问题:一是需要判断两个RelOptInfo是否可以进行连接,二是生成物理连接路径。目前物理连接路径主要有三种实现,分别是NestLoopJoin、HashJoin和MergeJoin,建立连接路径的过程就是不断地尝试生成这三种路径的过程。
1) 检查
在动态规划方法中需要将N-1层的每个RelOptInfo和第1层的每个RelOptInfo尝试做连接,然后将新连接的RelOptInfo保存在当前第N层,算法的时间复杂度在O(M×N)左右,如果发生第N-1层和第1层中RelOptInfo都比较多的情况下,搜索空间会膨胀得比较大。但有些RelOptInfo在做连接的时候是可以避免掉的,这也是我们需要及时检查的目的,提前检测出并且跳过两个RelOptInfo之间的链接,会节省不必要的开销,提升优化器生成优化的效率。
(1) 初步检查。
下面几个条件是初步检查主要进行衡量的因素。
- 一是ReOptinfo中joininfo不为NULL。那就说明这个ReOptInfo和其他的ReOptInfo存在相关的约束条件,换言之就是说当前这个ReOptInfo可能和其他表存在关联。
- 二是ReOptInfo中has_ecass_joins为true,表明在等价类的记录中当前ReOptInfo和其他ReOptInfo可能存在等值连接条件。
- 三是has_join_restriction函数的返回值为true,说明当前的ReOptInfo和其他的ReOptInfo有连接顺序的限制。
初步检查就是利用RelOptInfo的信息进行一种“可能性”的判断,主要是检测是否有连接条件和连接顺序的约束。
static bool has_join_restriction(PlannerInfo* root, RelOptInfo* rel)
{
ListCell* l = NULL;
//如果当前RelOptInfo涉及Lateral语义,那么就一定有连接顺序约束
foreach(l, root->lateral_info_list)
{
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
if (bms_is_member(ljinfo->lateral_rhs, rel->relids) ||
bms_overlap(ljinfo->lateral_lhs, rel->relids))
return true;
}
//仅处理除内连接之外的条件
foreach (l, root->join_info_list) {
SpecialJoinInfo* sjinfo = (SpecialJoinInfo*)lfirst(l);
//跳过全连接检查,会有其他机制保证其连接顺序
if (sjinfo->jointype == JOIN_FULL)
continue;
//如果这个SpecialJoinInfo已经被RelOptInfo包含就跳过
if (bms_is_subset(sjinfo->min_lefthand, rel->relids) &&
bms_is_subset(sjinfo->min_righthand, rel->relids))
continue;
//如果RelOptInfo结构体的relids变量和min_lefthand变量或min_righthand变量有交集,那么它就有可能有连接顺序的限制
if (bms_overlap(sjinfo->min_lefthand, rel->relids) ||
bms_overlap(sjinfo->min_righthand, rel->relids))
return true;
}
return false;
}
(1) 精确检查。
在进行了初步检查之后,如果判断出两侧RelOptInfo不存在有连接条件或者连接顺序的限制,那么就进入make_rels_by_clauseless_joins函数中,将RelOptInfo中所有可能的路径和第1层RelOptInfo进行连接。如果当前RelOptInfo可能有连接条件或者连接顺序的限制,那么就会进入make_rel_by_clause_joins函数中,会逐步将当前的RelOptInfo和第1层其他RelOptInfo进一步检查以确定是否可以进行连接。
在have_join_order_restriction函数判断两个RelOptInfo是否具有连接顺序的限制,主要从两个方面判断是否具有连接顺序的限制:一是判断两个RelOptInfo之间是否具有Lateral语义的顺序的限制,二是判断SpecialJoinInfo中的min_lefthand和min_righthand是否对两个RelOptInfo具有连接顺序的限制。
对have_join_order_restriction部分源码分析如下:
bool have_join_order_restriction(PlannerInfo* root, RelOptInfo* rel1, RelOptInfo* rel2)
{
bool result = false;
ListCell* l = NULL;
//如果有Lateral语义的依赖关系,则一定具有连接顺序的限制
foreach(l, root->lateral_info_list)
{
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
if (bms_is_member(ljinfo->lateral_rhs, rel2->relids) &&
bms_overlap(ljinfo->lateral_lhs, rel1->relids))
return true;
if (bms_is_member(ljinfo->lateral_rhs, rel1->relids) &&
bms_overlap(ljinfo->lateral_lhs, rel2->relids))
return true;
}
//遍历root目录中所有SpecialJoinInfo,判断两个RelOptInfo是否具有连接限制
foreach (l, root->join_info_list) {
SpecialJoinInfo* sjinfo = (SpecialJoinInfo*)lfirst(l);
if (sjinfo->jointype == JOIN_FULL)
continue;
//"最小集"分别是两个表的子集,两个表需要符合连接顺序
if (bms_is_subset(sjinfo->min_lefthand, rel1->relids) &&
bms_is_subset(sjinfo->min_righthand, rel2->relids)) {
result = true;
break;
}
//反过来同上,"最小集"分别是两个表的子集,两个表需要符合连接顺序
if (bms_is_subset(sjinfo->min_lefthand, rel2->relids) &&
bms_is_subset(sjinfo->min_righthand, rel1->relids)) {
result = true;
break;
}
//如果两个表都和最小集的一端有交集,那么这两个表应该在这一端下做连接
//故让他们先做连接
if (bms_overlap(sjinfo->min_righthand, rel1->relids) && bms_overlap(sjinfo->min_righthand, rel2->relids)) {
result = true;
break;
}
//反过来同上
if (bms_overlap(sjinfo->min_lefthand, rel1->relids) && bms_overlap(sjinfo->min_lefthand, rel2->relids)) {
result = true;
break;
}
}
//如果两个表上和其他表有相对应的连接关系
//那么可以让他们先和具有连接关系的表进行连接
if (result) {
if (has_legal_joinclause(root, rel1) || has_legal_joinclause(root, rel2))
result = false;
}
return result;
}
(2) 合法连接。
由于RelOptInfo会导致搜索空间膨胀,如果上来就对两个RelOptInfo进行最终的合法连接检查会导致搜索时间过长,这也就是为什么要提前做初步检查和精确检查的原因,可以减少搜索时间其实达到了“剪枝”的效果。
对于合法连接,主要代码在join_is_legal中,它主要就是判断两个RelOptInfo可不可以进行连接生成物理路径,入参就是两个RelOpInfo。对于两个待选的RelptInfo,仍不清楚他们之间的逻辑连接关系,有可能是Inner Join、LeftJoin、SemiJoin,或者压根不存在合法的逻辑连接关系,故这时候就需要确定他们的连接关系,主要分成两个步骤。
步骤1:对root中join_info_list链表中的SpecialJoinInfo进行遍历,看是否可以找到一个“合法”的SpecialJoinInfo,因为除InnerJoin外的其他逻辑连接关系都会生成对应的一个SpecialJoinInfo,并在SpecialJoinInfo中还记录了合法的链接顺序。
步骤2:对RelOptInfo中的Lateral关系进行排查,查看找到的SpecialJoinInfo是否符合Lateral语义指定的连接顺序要求。