openGauss之推断隐式谓词条件代码走读

一. 前言

     openGauss的隐式谓词推断是指通过SQL中明确的谓词条件推断出隐含的谓词条件,比如select * from test1 where b = 1 and a = b 可以直接推断出 a = 1的隐式谓词条件,并且将此谓词条件保存执行计划中,如下所示。正因为能推断出a = 1,所以才能使用a的索引条件加速。

openGauss=# explain select * from test1 where b = 1 and a = b;
                            QUERY PLAN
-------------------------------------------------------------------
 Index Scan using idxa on test1  (cost=0.00..8.27 rows=1 width=12)
   Index Cond: (a = 1)
   Filter: (b = 1)
(3 rows)

       本文主要通过走读openGauss的代码了解openGauss中是如何根据现有的已知约束来推断出一些隐含约束的。

二. 构建等价约束组

      openGauss首先会构建一个等价约束组EquivalenceClass,里边的所有成员都是等价的,也及时EquivalenceClass->ec_members全部为相互等价的成员。比如where b = 1 and a = b,会将b,1 和 a 组成一个EquivalenceClass,ec_members为b,1, a。 实现代码的流程如下所示:

deconstruct_recurse
    foreach (l, (List*)f->quals) {     // 对每一个谓词条件做处理
        distribute_qual_to_rels();
            process_equivalence    // 此函数会把所有等价谓词条件归类到同一个EquivalenceClass中,EquivalenceClass的ec_members中存放的所有成员都是相等的。
                item1 = (Expr*)get_leftop(clause);    // 等号左边
                item2 = (Expr*)get_rightop(clause);   // 等号右边
                foreach (lc1, root->eq_classes) {   // 看下当前的等价组中是否与当前等号等价
                    foreach (lc2, cur_ec->ec_members) {
                        if (equal(item1, cur_em->em_expr)) {  // 等号左边与等价组等价,那么接下来需要做的是把右边也添加到等价组中
                            ec1 = cur_ec;
                            em1 = cur_em;
                        }
                        if (equal(item2, cur_em->em_expr)) {  // 等号右边与等价组等价,那么接下来需要做的是把左边也添加到等价组中
                            ec2 = cur_ec;
                            em2 = cur_em;
                        }
                    }
                }
                if (ec1 != NULL && ec2 != NULL) {
                    if (ec1 == ec2) {
                        // 等号左右都在一个等价组中了,因此无需做额外的操作了
                    } else {
                       // 左边有等价组,右边也有等价组,但是左右属于不同的等价组,说明了这两个等价组也是完全等价的,那么合并两个等价组成一个等价组中
                       ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members);   // 合并等价组成一个
                           
                    }
                }
                else if (ec1 != NULL) { // 等号左边与等价组等价,右边不在等价组中
                    em2 = add_eq_member(ec1, item2, item2_relids, item2_nullable_relids, false, item2_type);  // 把等号右边的item2加入到等价组中
                } else if (ec2 != NULL) { { // 等号右边与等价组等价,左边不在等价组中 
                    em1 = add_eq_member(ec2, item1, item1_relids, item1_nullable_relids, false, item1_type);  // 把左边的item1加入到等价组中
                } else {  // 当前的左右都没有找到等价组,那么新建一个等价组,将当前等号的左右加入到组成员中
                    EquivalenceClass* ec = makeNode(EquivalenceClass);  // 新建等价组
                    em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids, false, item1_type);  // 左边加入到等价组中
                    em2 = add_eq_member(ec, item2, item2_relids, item2_nullable_relids, false, item2_type);   // 右边加入到等价组中
                           root->eq_classes = lappend(root->eq_classes, ec);
                }
        }

三. 基于等价条件组推断隐式谓词条件

      有了等价组后,进行隐藏的谓词推断则实现比较简单了,主要分成如下两种场景:

      场景1: 等价组包含常量的场景,此场景实现主要是将常量和所有非场常量重新生成谓词即可。

generate_base_implied_equalities_const
    foreach (lc, ec->ec_members) {
        EquivalenceMember* cur_em = (EquivalenceMember*)lfirst(lc);

        if (cur_em->em_is_const) {
            const_em = cur_em;
            if (IsA(cur_em->em_expr, Const))        // 从等价组中提取出常量项
                break;
        }
    }
    
    foreach (lc, ec->ec_members) {
        EquivalenceMember* cur_em = (EquivalenceMember*)lfirst(lc);  // 遍历等价组中的所有非常量成员,和常量生成新的谓词关系
        eq_op = select_equality_operator(ec, cur_em->em_datatype, const_em->em_datatype);     // 根据数据类型选择对应的等于操作函数
        
        process_implied_equality(....cur_em->em_expr, const_em->em_expr....) // 重新生成谓词关系,const_em->em_expr和const_em->em_expr是谓词的左右条件
           clause = make_opclause   // 生成新的谓词
               distribute_qual_to_rels  // 将谓词下推到relation中
                  restrictinfo = make_restrictinfo(clause)
                  distribute_restrictinfo_to_rels(restrictinfo)
                     rel->baserestrictinfo = lappend(rel->baserestrictinfo, restrictinfo);   // relation中保持谓词 
                             
    }

      场景2: 谓词中不包含常量的场景

      谓词中不包含常量的话,那么需要做的是将属于同一个表中两两条件互相生成谓词条件,比如,比如a.id1 = b.id1 and b.id1 = a.id2,将同属于a表中的id1和id2重新生成谓词a.id1 = a.id2,实现代码如下所示:

generate_base_implied_equalities_no_const
     prev_ems = palloc0(root->simple_rel_array_size * sizeof(EquivalenceMember*));  // prev_ems是个数组,下标为relation id,存放的是表中当前的约束成员
     foreach (lc, ec->ec_members) {
         EquivalenceMember* cur_em = (EquivalenceMember*)lfirst(lc);  // 约束条件
         
         relid = bms_singleton_member(cur_em->em_relids);  // 当前约束所在的表
         if (prev_ems[relid] != NULL) {   // 如果当前约束所在的表已经有约束了,那么将当前约束和已存在的约束组成新的谓词条件
             EquivalenceMember* prev_em = prev_ems[relid];  // 当前约束条件
             eq_op = select_equality_operator  // 根据类型选择等于操作符
             process_implied_equality(prev_em->em_expr, cur_em->em_expr) // 生成新的谓词条件,并且下推到relation中
                 ....  // 上边与上述带常量的约束条件是一样的了
         }
         prev_ems[relid] = cur_em;   // 保存当前约束到数组中的对应表中
     }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值