openGauss源码解析(94)

openGauss源码解析:SQL引擎源解析(9)

2. 数据分布抽取方法

数据分布的存储给出了数据分布在openGauss的逻辑结构和存储方式。那么上面介绍的数据分布信息是如何从数据中获得呢?针对该问题,下面将简要介绍openGauss抽取分布的主要过程。为加深对方法的理解,先分析该问题面临的挑战。

获取分布最直接的办法是遍历所有数据,并通过计数直接生成MCV和直方图信息。但现实中的数据可能是海量的,遍历的I/O代价往往不可接受。比如,银行的账单数据涉及上千亿条记录,需要TB级的存储。除I/O代价外,计数过程的内存消耗也可能超过上限,这也使得算法实现变得尤为困难。因此,更现实的做法是降低数据分析的规模,采用小样本分析估算整体数据分布。那么,样本选择的好坏就显得尤为重要。

目前,openGauss数据库的样本生成过程在acquire_sample_rows函数实现,它采用了两阶段采样的算法对数据分布进行估算。第一阶段使用S算法对物理页进行随机采样,生成样本S1;第二阶段使用Z(Vitter)算法对S1包含的元组进行蓄水池采样,最终生成一个包含3000元组的样本S2。两阶段算法可以保证S2是原数据的一个无偏样本。因此,可以通过分析S2推断原数据分布,并将分布信息记录在PG_STATISTIC表的对应元组中。

openGauss将样本的生成划分成两个步骤,主要是为了提高采样效率。该方法的理论依据依赖于以下现实条件:数据所占据的物理页数量M可以准确获得,而每个物理页包含的元组数n未知。由于M已知,S算法可以用1/M的概率对页进行均匀抽样,可以生成原数据的小样本S1。一般认为,某元组属于任一物理页是等概率事件,这就保证了S1是一个无偏样本;而由于S1包含的元组远少于原数据,在S1的基础上进行二次抽样代价将大大减少。第二阶段没有继续使用S算法的主要原因是:S1的元组总数N未知(因为n未知),该算法无法获得采样概率——1/N。而Z(Vitter)的算法是一种蓄水池抽样算法,这类算法可以在数据总量未知条件下保证采样的均匀。蓄水池抽样算法原理不是本书的重点,读者可以自行查阅资料。

3. 选择率与数据分布

SQL查询常常带有where约束(过滤条件),比如:Select * from student where gender = ‘male’; Select * from student where grade > ‘1’。那么,约束对于查询结果的实际影响是什么呢?为度量约束的效能,首先引入选择率的概念。

选择率:给定查询数据集CC可为数据表或任何中间结果集合)和约束表达式xx相对C的选择率定义为

其中,表示C的总记录数,表示C上满足x约束的记录数。如表6-13所示,在x为“grade = 1”时,3/5。

表6-13 数据集C选择率结果

Sno

Name

Gender

Grade

001

小张

1

002

小李

2

003

小王

3

004

小周

1

005

小陈

1

C的数据分布为π。从定义可知,其实是对π按照语义x的一种描述。从这里可看到数据分布的关键用处:数据分布可以辅助选择率的计算、而使得计算过程不必遍历原数据。在代价估算部分中,将看到选择率对计划代价估算的巨大作用。

根据该思路,介绍openGauss计算选择率的基本过程。注意,由于简单约束下的选择率计算具有代表性,本部分将主要围绕着该进行问题进行讲解。简单约束的定义为:仅涉及基表单个属性的非范围约束。

涉及非简单约束选择率的计算方法,读者可以参照本章自行阅读源码。

1) 简单约束的选择率计算

假设x为简单约束,且x所涉及的属性分布信息已存在于PG_STATISTIC表元组r中(参见数据分布的存储部分内容)。openGauss通过调用clause_selectivity函数将元组rx要求转换为选择率。

clause_selectivity的第二个参数clause为约束语句x。面对不同SQL查询,输入clause_selectivity的clause可能有多种类型,典型类型如表6-14所示。

表6-14 简单约束类型

简单约束类型

实例

Var

SELECT * FROM PRODUCT WHERE ISSOLD;

Const

SELECT * FROM PRODUCT WHERE TRUE;

Param

SELECT * FROM PRODUCT WHERE $1;

OpExpr

SELECT * FROM PRODUCT WHERE PRIZE = ‘100’;

AND

SELECT * FROM PRODUCT WHERE PRIZE = ‘100’ AND TYPE = ‘HAT’;

OR

SELECT * FROM PRODUCT WHERE PRIZE = ‘100’ OR TYPE = ‘HAT’;

NOT

SELECT * FROM PRODUCT WHERE NOT EXIST TYPE = ‘HAT’;

其他

 

{Var, Const, Param, OpExpr}属于基础约束类型,而包含{AND, OR, NOT}的约束都是建立约束基础上的集合运算,称为SET约束类型。进一步观察可以发现,约束{Var, Const, Param}可以看作OpExpr约束的一个特例。比如:“SELECT * FROM PRODUCT WHERE ISSOLD”与“SELECT * FROM PRODUCT WHERE ISSOLD = TRUE”等价。限于篇幅,这里将着重介绍基于OpExpr类型的选择率计算,并简要给出SET类型计算的关键逻辑。

(1) OpExpr类型选择率。

以查询语句SELECT * FROM PRODUCT WHERE PRIZE = ‘100’为例。clause_selectivity函数首先根据clause(PRIZE = ‘100’)类型找到OpExpr分支。然后调用treat_as_join_clause函数判断clause是否是一个join约束;结果为假,说明clause是过滤条件(OP),则调用restriction_selectivity函数对clause参数进行选择率估算。代码如下:

Selectivity

clause_selectivity(PlannerInfo *root,

Node *clause,

int varRelid,

JoinType jointype,

SpecialJoinInfo *sjinfo)

{

Selectivity s1 = 0.5;/* default for any unhandled clause type */

RestrictInfo *rinfo = NULL;

if (clause == NULL) /* can this still happen? */

return s1;

if (IsA(clause, Var))...

else if (IsA(clause, Const))...

else if (IsA(clause, Param))

 

// not子句处理分支

else if (not_clause(clause))

{

/* inverse of the selectivity of the underlying clause */

s1 = 1.0 - clause_selectivity(root,

(Node *) get_notclausearg((Expr *) clause),

varRelid,

jointype,

sjinfo);

}

 

// and子句处理分支

else if (and_clause(clause))

{

/* share code with clauselist_selectivity() */

s1 = clauselist_selectivity(root,

((BoolExpr *) clause)->args,

varRelid,

jointype,

sjinfo);

}

 

// or子句处理分支

else if (or_clause(clause))

{

ListCell *arg;

s1 = 0.0;

foreach(arg, ((BoolExpr *) clause)->args)

{

Selectivity s2 = clause_selectivity(root,

(Node *) lfirst(arg),

varRelid,

jointype,

sjinfo);

s1 = s1 + s2 - s1 * s2;

}

}

 

// join或op子句处理分支

else if (is_opclause(clause) || IsA(clause, DistinctExpr))

{

OpExpr *opclause = (OpExpr *) clause;

Oidopno = opclause->opno;

// join子句处理

if (treat_as_join_clause(clause, rinfo, varRelid, sjinfo))

{

/* Estimate selectivity for a join clause. */

s1 = join_selectivity(root, opno,

opclause->args,

opclause->inputcollid,

jointype,

sjinfo);

}

 

// op子句处理

else

{

/* Estimate selectivity for a restriction clause. */

s1 = restriction_selectivity(root, opno,

opclause->args,

opclause->inputcollid,

varRelid);

}

}

... ...

return s1;

}

restriction_selectivity函数识别出PRIZE = ‘100’是形如Var = Const的等值约束,它将通过eqsel函数间接调用var_eq_const函数进行选择率估算。在该过程中,var_eq_const函数会读取PG_STATISTIC表中PRIZE列分布信息,并尝试利用信息中MCV计算选择率。首选调用get_attstatsslot函数判断‘100’是否存在于MCV中,有以下几种情况。

  • 情况1:存在,直接从MCV中返回‘100’的占比作为选择率。
  • 情况2:不存在,则计算高频值的总比例sumcommon,并返回(1.0 – sumcommon – nullfrac) / otherdistinct作为选择率。其中,nullfrac是NULL的比例,otherdistinct是低频值的NDV。

加入查询的约束是PRIZE < ‘100’,restriction_selectivity函数,该约束将根据操作符类型调用scalargtsel函数并尝试利用PG_STATISTIC表中信息计算选择率。由于满足< ‘100’的值可能分别存在于MCV和直方图中,所以需要分别在两种结构中收集满足条件的值。相比于MCV来说,在直方图中收集满足条件值的过程较为复杂,因此下面重点介绍:借助于直方图key的有序性,openGauss采用二分查找快速搜寻满足条件的值,并对其总占比进行求和并记作selec_histogram。注意,等高直方图不会单独记录‘100’的频次,而是将‘100’和相邻值合并放入桶(记作B桶)中,并仅记录B中数值的总频次(Fb)。为解决该问题,openGauss假设桶中元素频次相等,并采用公式估算B中满足条件值的占比。该过程的具体代码实现在ineq_histogram_selectivity函数中。最终,restriction_selectivity函数返回的选择率值为selec =selec_mcv + selec_histogram,其中,selec_mcv是MCV中满足条件的占比。

(2) SET类型选择率。

对于SET类型约束,clause_selectivity函数递归计算其包含的基本约束选择率。然后根据SET类型的语义,通过表6-15所列方式返回最终选择率。

表6-15 SET类型选择率说明

SET类型

说明

NOT运算符

selec(B) = 1 –selec(A) {B = NOT A}

AND运算符

{A AND B}

OR运算符

{A OR B}

回顾数据分布的存储部分的内容,openGauss并没有保存多属性联合分布。而从表6-15可以看出,openGauss是在不同列取值相互独立的假设下对联合分布进行推算的。在列不独立的场景下,这种预测常常存在偏差。例如:对于学生表来说,性别和专业存在相关性。因此,不能通过男同学占比×计算机系人数去推测该系的人数。但在一般情况下,使用独立的假设往往也能获得较准确的结果。

(3) 选择率默认参数。

在数据分布未知的情况下,不能通过常规方法对选择率进行估算。例如:未对数据表进行analyze操作,或者过滤条件本身就是一个不确定的参数。为给优化器一个合理的参考值,openGauss给出了一系列选择率的经验参数,如表6-16所示。

表6-16 选择率参数说明

变量名

说明

DEFAULT_EQ_SEL

0.005

为等值约束条件的默认选择率,例如A = b

DEFAULT_INEQ_SEL

0.3333333333333333

为不等值约束条件的默认选择率,例如A < b

DEFAULT_RANGE_INEQ_SEL

0.005

为涉及同一个属性(列)的范围约束条件的默认选额率,例如A > b AND A < c

DEFAULT_MATCH_SEL

0.005

为基于模式匹配的约束条件的默认选择率,例如LIKE

DEFAULT_NUM_DISTINCT

200

为对一个属性消重(distinct)之后的值域中有多少个元素,通常和DEFAULT_EQ_SEL互为倒数

DEFAULT_UNK_SEL

0.005

为对BoolTest或NullText这种约束条件的默认选择率,例如IS TRUE或IS NULL

DEFAULT_NOT_UNK_SEL

(1.0 - DEFAULT_UNK_SEL)

为对BoolTest或NullText这种约束条件的默认选择率,例如IS NOT TRUE或IS NOT NULL

posted @ 2024-04-30 10:11  openGauss-bot  阅读(11)  评论(0编辑  收藏  举报