一. 前言
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; // 保存当前约束到数组中的对应表中
}