openGauss源码学习(二)选择率估算

openGauss源码学习(二)选择率估算

上一篇:EXPLAIN计划分析



前言

上一篇举例子说明了在计划生成过程中,算子可能出现行数偏差较大的情况。那么这篇就从代码的角度分析一下OG是怎么估算表的行数,以及为什么有时候出现这么大的偏差。


一、基表行数估算

基表是指普通表、临时表等类型的表。基表的统计信息介绍和行数估算可以参考博文《计划生成揭秘》。简单来说,基表的行数估算是基于统计信息的,如果基表存在过滤条件,那么,行数 = 基表总行数 * 过滤条件选择率。

1. 单个条件选择率估算

条件选择率一般是根据mcv值和直方图去估算,还是以上次的简单例子来说明,a=1这个条件比较准确地估算出SeqScan算子的行数只有一行,这个就是使用mcv值去计算的。

CREATE TABLE t1(a INT, b INT);
INSERT INTO t1 SELECT x,x FROM generate_series(1,10) x; -- 插入10条数据
ANALYZE t1;
EXPLAIN ANALYZE SELECT * FROM t1 WHERE a=1;
                                         QUERY PLAN
--------------------------------------------------------------------------------------------
 Seq Scan on t1  (cost=0.00..1.12 rows=1 width=8) (actual time=0.037..0.044 rows=1 loops=1)
   Filter: (a = 1)
   Rows Removed by Filter: 9
 Total runtime: 0.238 ms
(4 rows)

单个约束条件选择率估算的入口函数是restriction_selectivity,实际计算选择率的函数是scalarltsel_internal,对这个函数的源码做一下分析:

float8 scalarltsel_internal(PlannerInfo* root, Oid opera, List* args, int varRelid)
{
    ...
    /*
     * If expression is not variable op something or something op variable,
     * then punt and return a default estimate.
     */
    if (!get_restriction_variable(root, args, varRelid, &vardata, &other, &varonleft))
        // 先检查是否是列 operator 常量,如果不是(比如a=b)则选取固定的选择率。
        return DEFAULT_INEQ_SEL; // 0.3333333333333333

    // 对于常量的值做检查,一些特殊情况也会根据情况选取固定的选择率。
    ...

	// vardata是get_restriction_variable中通过examin_variable函数获取的
	// scalarineqsel函数内根据统计信息去估算选择率
    selec = scalarineqsel(root, opera, isgt, &vardata, constval, consttype);

    ReleaseVariableStats(vardata);

    return (float8)selec;
}

主要逻辑集中在examine_variable函数(根据var获取基表统计信息)和scalarineqsel(根据统计信息计算选择率),我们这次先分析下用例对应的短路径代码。

void examine_variable(PlannerInfo* root, Node* node, int varRelid, VariableStatData* vardata)
{
    ...
     /* Fast path for a simple Var */
     // a=3的querytree结构类似于
     // opr =
     //    larg: var 'a' varno=1 varattno=1 说明是query中的第一个表的第一列
     //    rarg: const '3'
    if (IsA(basenode, Var) && (varRelid == 0 || (uint)varRelid == ((Var*)basenode)->varno)) {
        Var* var = (Var*)basenode;

        /* Set up result fields other than the stats tuple */
        vardata->var = basenode; /* return Var without relabeling */
        vardata->rel = find_base_rel(root, var->varno); // 从simple_rel_array中获取表的物理信息
        vardata->atttype = var->vartype;
        vardata->atttypmod = var->vartypmod;
        vardata->isunique = has_unique_index(vardata->rel, var->varattno);

        /* Try to locate some stats */
        // 函数内的逻辑是获取rte的统计信息,普通表则是直接使用表oid去pg_statisic系统表中查询。
        examine_simple_variable(root, var, vardata);

		// 到这里就通过短路径获取到了一个simple var对应的单列统计信息了。
        return;
    }
    ...
    // 后续的逻辑是处理更复杂的表达式,比如var是join后的列,或者var涉及表达式(a+3=4)这种情况等等。
}
static double scalarineqsel(
    PlannerInfo* root, Oid opera, bool isgt, VariableStatData* vardata, Datum constval, Oid consttype)
{
    ...
    if (!HeapTupleIsValid(vardata->statsTuple)) {
        /* no stats available, so default result */
        return DEFAULT_INEQ_SEL; // 0.3333333333333333
    }
    stats = (Form_pg_statistic)GETSTRUCT(vardata->statsTuple);

    fmgr_info(get_opcode(opera), &opproc);
    equaloperator = get_equal(opera);

    /*
     * If we have most-common-values info, add up the fractions of the MCV
     * entries that satisfy MCV OP CONST.  These fractions contribute directly
     * to the result selectivity.  Also add up the total fraction represented
     * by MCV entries.
     */
     // mcv_selectivity负责遍历统计信息中的所有mcv值,并确认是否有满足条件的mcv值。
    mcv_selec = mcv_selectivity(vardata, &opproc, constval, true, &sumcommon, equaloperator, &inmcv, &lastcommon);
    
    /*
     * If there is a histogram, determine which bin the constant falls in, and
     * compute the resulting contribution to selectivity.
     */
     // 根据直方图估算条件的选择率,多用于估算范围条件,比如大于小于等
    hist_selec = ineq_histogram_selectivity(root, vardata, &opproc, isgt, constval, consttype);

    /*
     * Now merge the results from the MCV and histogram calculations,
     * realizing that the histogram covers only the non-null values that are
     * not listed in MCV.
     */
    selec = 1.0 - stats->stanullfrac - sumcommon;

    if (hist_selec >= 0.0)
        selec *= hist_selec;
    else {
        /*
         * If no histogram but there are values not accounted for by MCV,
         * arbitrarily assume half of them will match.
         */
        selec *= 0.5;
    }

    selec += mcv_selec;

	// 根据guc对某些特定的估算不准的场景进行选择率修正
    ...

    return selec;
}

二、多个条件的选择率

clauselist_selectivity函数对整个表达式的选择率进行了估算,首先是对于每个clause进行估算,然后根据类型再做合并处理。

1. AND/OR

OR条件选择率估算模型:

  s1 = 0.0;
        foreach (arg, ((BoolExpr*)clause)->args) {
            /* DO NOT cache the var ratio of single or-clauses */
            Selectivity s2 = clause_selectivity(root, (Node*)lfirst(arg), varRelid, jointype, sjinfo, false);

            s1 = s1 + s2 - s1 * s2;
        }

AND条件选择率估算模型:

    /* Not the right form, so treat it generically. */
    // 因为AND条件的列可能存在相关性,OG暂时不支持收集相关性统计信息
    // 提供了guc参数cost_param可以调整AND条件的估算方式。
    if ((uint32)u_sess->attr.attr_sql.cost_param & COST_ALTERNATIVE_CONJUNCT) {
        s1 = MIN(s1, s2);
        expr->xpr.selec = s1;
    } else {
        s1 = s1 * s2;
        expr->xpr.selec = s2;
    }

2. 范围选择率修正

clauselist_selectivity中也对范围条件进行了选择率修正。

        /* Successfully matched a pair of range clauses */
        Selectivity s2;
        /*
         * Exact equality to the default value probably means the
         * selectivity function punted.  This is not airtight but should
         * be good enough.
         */
        if (rqlist->hibound == DEFAULT_INEQ_SEL || rqlist->lobound == DEFAULT_INEQ_SEL) {
             s2 = DEFAULT_RANGE_INEQ_SEL;
        } else {
            // 优化器对于 a > 1 AND a < 5这种场景进行了修正
            // 没有像之前说的等值条件那样,直接对AND的两个条件选择率相乘
            // 而是对对应的pair计算重叠部分的比例。
            s2 = rqlist->hibound + rqlist->lobound - 1.0;
            /* Adjust for double-exclusion of NULLs */
            s2 += nulltestsel(root, IS_NULL, rqlist->var, varRelid, jointype, sjinfo);

            /*
             * A zero or slightly negative s2 should be converted into a
             * small positive value; we probably are dealing with a very
             * tight range and got a bogus result due to roundoff errors.
             * However, if s2 is very negative, then we probably have
             * default selectivity estimates on one or both sides of the
             * range that we failed to recognize above for some reason.
            */
            if (s2 <= 0.0) {
                if (s2 < -0.01) {
                    /*
                     * No data available --- use a default estimate that
                     * is small, but not real small.
                     */
                    s2 = DEFAULT_RANGE_INEQ_SEL;
                 } else {
                    /*
                     * It's just roundoff error; use a small positive
                     * value
                     */
                    s2 = 1.0e-10;
                }
            }
        }

总结

这篇博文对几个简单场景的选择率估算进行了说明,还有很多其他的复杂场景计划在后续的博文中进行分析说明。但是从代码实现和估算机制的角度来看,很多场景(尤其是复杂场景)PG/OG并没有办法给出一个准确的选择率,只能根据经验或者假设来获取到一个固定的值。在子查询、条件涉及列运算的话,一般会出现更多估算误差较大的情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值