openGauss数据库源码解析 | SQL引擎源解析(13)

本文详细描述了数据库优化器在路径生成过程中,通过动态规划方法处理RelOptInfo连接的问题,包括初步检查和精确检查规则,以及如何通过NestLoopJoin、HashJoin和MergeJoin等方式生成物理连接路径,以提升优化效率。
摘要由CSDN通过智能技术生成
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) 初步检查。

下面几个条件是初步检查主要进行衡量的因素。

  1. 一是ReOptinfo中joininfo不为NULL。那就说明这个ReOptInfo和其他的ReOptInfo存在相关的约束条件,换言之就是说当前这个ReOptInfo可能和其他表存在关联。
  2. 二是ReOptInfo中has_ecass_joins为true,表明在等价类的记录中当前ReOptInfo和其他ReOptInfo可能存在等值连接条件。
  3. 三是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语义指定的连接顺序要求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值