• 【 OpenGauss源码学习 —— 列存储(update_pages_and_tuples_pgclass)】


    声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
    本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些学习资料

    概述

      本文所学习的重点依旧围绕列存储展开。

    update_pages_and_tuples_pgclass 函数

      update_pages_and_tuples_pgclass 函数的主要功能是在 PostgreSQL 数据库中更新表和索引的统计信息,特别是更新 pg_class 中的关系页面数总行数总死行数等统计数据。它还计算并更新一个表扩展因子,用于查询优化。此函数还负责对表的索引进行统计信息的更新,以帮助查询优化器做出更准确的决策。
      update_pages_and_tuples_pgclass 函数源码如下所示:(路径:src/gausskernel/optimizer/commands/analyze.cpp

    /*
     * update_pages_and_tuples_pgclass: 更新用于分析到pg_class的关系页面和元组。
     *
     * 参数:
     *	@in onerel: 用于分析或清理的关系
     *	@in vacstmt: 用于分析或清理命令的语句
     *	@in attr_cnt: 用于分析的属性计数
     *	@in vacattrstats: 所有属性的统计结构,或通过分析命令索引的统计结构,用于计算并更新pg_statistic。
     *	@in hasindex: 是否存在索引用于分析
     *	@in nindexes: 分析的索引数量
     *	@in indexdata: 用于分析的每个索引的数据
     *	@in Irel: 为分析malloc的新索引关系
     *	@in relpages: 关系页面数
     *	@in totalrows: 关系的总行数
     *	@in totaldeadrows: 关系的总死行数
     *	@in numrows: 样本行数
     *	@in inh: 是否为继承表进行分析
     *
     * 返回: 无
     */
    static void update_pages_and_tuples_pgclass(Relation onerel, VacuumStmt* vacstmt, int attr_cnt,
        VacAttrStats** vacattrstats, bool hasindex, int nindexes, AnlIndexData* indexdata, Relation* Irel,
        BlockNumber relpages, double totalrows, double totaldeadrows, int64 numrows, bool inh)
    {
        BlockNumber updrelpages = relpages;
        /*
         * 当我们从dn1接收pg_class时,我们已在ReceivePageAndTuple函数中更新了pg_class,
         * 因此,此处仅在数据节点上更新pg_class。
         */
        if (IS_PGXC_DATANODE) {
            BlockNumber mapCont = 0;
    
            /* 更新列存储表的relpages */
            if (RelationIsColStore(onerel)) {
                /*
                 * 对于CU格式和PAX格式,由于RelationGetNumberOfBlocks返回0,因此我们必须从totalrows生成relpages
                 *
                 * 最佳方法是使用所有行(活动和死行),但为了确保向前兼容性,我们只能处理所有行都为死行的情况。
                 */
                double allrows = totalrows > 0 ? totalrows : totaldeadrows;
    
                /* 如果关系是DFS表,则从HDFS文件获取relpage。 */
                if (RelationIsPAXFormat(onerel)) {
                    updrelpages = estimate_cstore_blocks(onerel, vacattrstats, attr_cnt, numrows, allrows, true);
                } else {
                    updrelpages = estimate_cstore_blocks(onerel, vacattrstats, attr_cnt, numrows, allrows, false);
                }
            }
    #ifdef ENABLE_MULTIPLE_NODES
            else if (RelationIsTsStore(onerel)) {
                updrelpages = estimate_tsstore_blocks(onerel, attr_cnt, totalrows);
            }
    #endif   /* ENABLE_MULTIPLE_NODES */
    
            if (RelationIsPartitioned(onerel)) {
                Relation partRel = NULL;
                ListCell* partCell = NULL;
                Partition part = NULL;
    
                foreach (partCell, vacstmt->partList) {
                    part = (Partition)lfirst(partCell);
                    partRel = partitionGetRelation(onerel, part);
                    mapCont += visibilitymap_count(onerel, part);
                    releaseDummyRelation(&partRel);
                }
            } else {
                mapCont = visibilitymap_count(onerel, NULL);
            }
    
            Relation classRel = heap_open(RelationRelationId, RowExclusiveLock);
            vac_update_relstats(onerel, classRel, updrelpages, totalrows, mapCont, hasindex, InvalidTransactionId);
            heap_close(classRel, RowExclusiveLock);
        }
    
        /* 估算表扩展因子 */
        double table_factor = 1.0;
        /* 使用GUC参数PARAM_PATH_OPTIMIZATION计算table_factor */
        if (ENABLE_SQL_BETA_FEATURE(PARAM_PATH_OPT) && onerel && onerel->rd_rel) {
            BlockNumber pages = onerel->rd_rel->relpages;
            /* 在此处重用索引估算(基于相同类型的估算更好) */
            double estimated_relpages = (double)estimate_index_blocks(onerel, totalrows, table_factor);
            table_factor = (double)pages / estimated_relpages;
            /* 如果表因子太小,则使用1.0 */
            table_factor = table_factor > 1.0 ? table_factor : 1.0;
        }
    
        /*
         * 对于索引,情况类似。清理总是扫描所有索引,因此如果我们在VACUUM的一部分,
         * 则不要覆盖VACUUM已插入的准确计数。
         */
        if (!((unsigned int)vacstmt->options & VACOPT_VACUUM) || ((unsigned int)vacstmt->options & VACOPT_ANALYZE)) {
            for (int ind = 0; ind < nindexes; ind++) {
                AnlIndexData* thisdata = &indexdata[ind];
                double totalindexrows;
                BlockNumber nblocks = 0;
    
                totalindexrows = ceil(thisdata->tupleFract * totalrows);
                /*
                 * @global stats
                 * 更新CN中发出分析的地方的索引的全局relpages和全局reltuples的pg_class。
                 */
                if (IS_PGXC_COORDINATOR && ((unsigned int)vacstmt->options & VACOPT_ANALYZE) &&
                    (0 != vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts)) {
                    nblocks = estimate_index_blocks(Irel[ind], totalindexrows, table_factor);
                    Relation classRel = heap_open(RelationRelationId, RowExclusiveLock);
                    vac_update_relstats(Irel[ind], classRel, nblocks, totalindexrows, 0, false, BootstrapTransactionId);
                    heap_close(classRel, RowExclusiveLock);
                    continue;
                }
    
                /* 不要为VACUUM更新pg_class。 */
                if ((unsigned int)vacstmt->options & VACOPT_VACUUM)
                    break;
    
                nblocks = GetOneRelNBlocks(onerel, Irel[ind], vacstmt, totalindexrows);
    
                Relation classRel = heap_open(RelationRelationId, RowExclusiveLock);
                vac_update_relstats(Irel[ind], classRel, nblocks, totalindexrows, 0, false, InvalidTransactionId);
                heap_close(classRel, RowExclusiveLock);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    这个函数的执行过程如下:

    1. 函数开始,它接受多个参数,包括一个表示要分析或清理的关系onerel)以及与该操作相关的其他信息和统计数据
    2. 首先,函数初始化一个名为 updrelpages 的变量,将其设置为与 relpages 相等,relpages 表示关系中的页面数
    3. 然后,函数检查当前会话是否在 PostgreSQL 数据节点上运行(IS_PGXC_DATANODE)。如果是在数据节点上执行,它会继续执行更新 pg_class 目录表中的统计信息的操作。这包括计算和更新与关系有关的各种统计数据,如 relpagestotalrowstotaldeadrows 等,这些统计数据对于查询优化非常重要。
    4. 如果关系是列存储表(RelationIsColStore(onerel)),则会根据特定的算法估算 relpages 的值。这个值是为了帮助数据库系统更好地了解表的物理存储结构
    5. 接着,它计算一个叫做 table_factor表扩展因子,用于查询优化。这个因子根据关系的页面数和一些估算值计算而来。
    6. 然后,函数会对关系的索引进行循环迭代,为每个索引计算统计信息,包括块数(nblocks)总索引行数。如果这是在协调器节点上执行 ANALYZE 操作,它会更新每个索引的 pg_class 中的统计信息。
    7. 最后,如果这不是一个 VACUUM 操作,函数还会更新每个索引pg_class 统计信息,并考虑之前计算的表扩展因子table_factor)。

    ReceivePageAndTuple 函数

      在函数 update_pages_and_tuples_pgclass 中提到:

    /*
    * we have updated pg_class in function ReceivePageAndTuple when we receive pg_class from dn1,
    * so, we only update pg_class on datanodes this place.
    */
    
    • 1
    • 2
    • 3
    • 4

      这里所表达的含义如下:

      ReceivePageAndTuple 函数和 update_pages_and_tuples_pgclass 函数之间的关系是,ReceivePageAndTuple 函数用于接收从远程节点传来的统计信息,并将这些信息用于更新 pg_class 中的页面、元组等统计数据。这是分布式数据库系统中的一种机制,用于从远程节点获取统计信息并将其同步到中央数据库。一般情况下,ReceivePageAndTuple 函数在远程节点执行,而 update_pages_and_tuples_pgclass 函数在中央数据库执行。
      具体来说,ReceivePageAndTuple 函数用于接收来自远程节点的统计信息并将其传递给中央数据库,然后中央数据库中的 update_pages_and_tuples_pgclass 函数会使用这些统计信息来更新 pg_class 中的相关数据,以维护关系的统计信息。这种机制允许分布式数据库系统在多个节点上分布式地维护更新统计信息,以支持查询优化性能调优

      ReceivePageAndTuple 函数源码如下所示:(路径:src/common/backend/pgxc_single/pool/execRemote.cpp

    /*
     * update pages, tuples, etc in pg_class
     * 更新pg_class中的页面、元组等信息
     */
    static void ReceivePageAndTuple(Oid relid, TupleTableSlot* slot, VacuumStmt* stmt)
    {
        Relation rel;
        Relation classRel;
        RelPageType relpages;
        double reltuples;
        BlockNumber relallvisible;
        bool hasindex = false;
    
        // 从TupleTableSlot中获取并初始化关系的统计信息
        relpages = (RelPageType)DatumGetFloat8(slot->tts_values[0]);
        reltuples = (double)DatumGetFloat8(slot->tts_values[1]);
        relallvisible = (BlockNumber)DatumGetInt32(slot->tts_values[2]);
        hasindex = DatumGetBool(slot->tts_values[3]);
    
        // 打开关系和pg_class表
        rel = relation_open(relid, ShareUpdateExclusiveLock);
        classRel = heap_open(RelationRelationId, RowExclusiveLock);
    
        // 调用vac_update_relstats函数来更新pg_class中的统计信息
        vac_update_relstats(rel, classRel, relpages, reltuples, relallvisible, hasindex, BootstrapTransactionId);
    
        /* 保存标识是否有脏数据的标志到stmt中 */
        if (stmt != NULL) {
            stmt->pstGlobalStatEx[stmt->tableidx].totalRowCnts = reltuples;
        }
    
        /*
         * 从远程DN/CN不获取已删除元组的信息,仅将deadtuples设置为0。
         * 这不会有影响,因为我们应该从所有数据节点获取已删除元组的信息来计算用户定义表的已删除元组信息。
         */
        if (!IS_PGXC_COORDINATOR || IsConnFromCoord())
            pgstat_report_analyze(rel, (PgStat_Counter)reltuples, (PgStat_Counter)0);
    
        // 关闭pg_class表和关系
        heap_close(classRel, NoLock);
        relation_close(rel, NoLock);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    estimate_cstore_blocks 函数

      函数 estimate_cstore_blocks 用于估算列存储表所需的页数,以便进行资源分配存储优化。它考虑了元组的宽度总元组数以及存储格式DFS或非DFS),并根据这些因素计算出所需的总页数。这对于数据库的存储查询性能优化非常重要。
      estimate_cstore_blocks 函数源码如下所示:(路径:src/gausskernel/optimizer/commands/analyze.cpp

    static BlockNumber estimate_cstore_blocks(
        Relation rel, VacAttrStats** vacAttrStats, int attrCnt, double sampleTuples, double totalTuples, bool dfsStore)
    {
        int tuple_width = 0; // 初始化元组宽度为0
        BlockNumber total_pages = 0; // 初始化总页数为0
        int i;
        bool isPartition = RelationIsPartition(rel); // 检查关系是否为分区表
    
        if (totalTuples <= 0) {
            return 0; // 如果总元组数小于等于0,返回0页
        }
    
        /* 计算元组宽度,循环遍历关系的属性 */
        for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++) {
            Form_pg_attribute att = rel->rd_att->attrs[i - 1]; // 获取属性信息
            int32 item_width = -1; // 初始化属性宽度为-1
    
            if (att->attisdropped)
                continue; // 如果属性被删除,跳过
    
            item_width = get_typlen(att->atttypid); // 获取属性的类型宽度
            if (item_width > 0) {
                /* 对于定长数据类型,累加宽度 */
                tuple_width += item_width;
                continue;
            }
    
            if (sampleTuples > 0) {
                /* 从当前正在执行的统计信息中获取 stawidth */
                bool found = false;
    
                for (int attIdx = 0; attIdx < attrCnt; ++attIdx) {
                    VacAttrStats* stats = vacAttrStats[attIdx];
    
                    if (att->attnum == stats->tupattnum) {
                        item_width = stats->stawidth;
                        found = true;
                        break;
                    }
                }
    
                if (found) {
                    tuple_width += item_width;
                    continue;
                }
            }
    
            if (item_width <= 0) /* 从现有的统计信息中获取 stawidth */
                item_width = get_attavgwidth(RelationGetRelid(rel), i, isPartition);
    
            if (item_width <= 0) /* 获取属性类型值的平均宽度 */
                item_width = get_typavgwidth(att->atttypid, att->atttypmod);
    
            Assert(item_width > 0);
    
            tuple_width += item_width; // 累加属性宽度到元组宽度
        }
    
        if (dfsStore)
            total_pages = ceil((totalTuples * tuple_width * ESTIMATE_BLOCK_FACTOR) / BLCKSZ);
        else
            total_pages = ceil(totalTuples / RelDefaultFullCuSize) * (RelDefaultFullCuSize * tuple_width / BLCKSZ);
    
        if (totalTuples > 0 && totalTuples < total_pages)
            total_pages = totalTuples; // 如果总元组数小于总页数,将总页数设为总元组数
    
        if (totalTuples > 0 && total_pages <= 0)
            total_pages = 1; // 如果总元组数大于0且总页数小于等于0,将总页数设为1
    
        return total_pages; // 返回估算的总页数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    注:stawidth 表示统计信息中的一个字段,它用于存储某一属性的估算宽度width这个宽度是指在关系中存储该属性的平均字节数。在数据库统计信息中,为了更好地进行查询优化和成本估算,通常会估算每个属性的宽度,这有助于数据库系统决定如何在磁盘上存储数据以及如何执行查询计划
      具体来说,stawidth 存储了该属性的平均宽度,它是根据对表的样本数据进行统计估算得出的。这个估算值可以用于计算数据存储和传输成本,以便数据库系统可以更好地规划查询执行计划资源分配
      在数据库中,属性的宽度是指该属性的数据在磁盘上占用的字节数。不同的数据类型(例如整数文本日期等)具有不同的宽度。因此,估算每个属性的宽度对于数据库系统来说是非常重要的,它有助于优化查询性能和资源利用
      stawidth 通常在数据库的统计信息中使用,以帮助查询优化器做出更好的决策。在上面的代码示例中,estimate_cstore_blocks 函数使用 stawidth 来估算列存储表的页数,以帮助数据库管理和查询优化

    get_attavgwidth 函数

      在 estimate_cstore_blocks 函数中使用 get_attavgwidth 函数来查询表的属性的平均宽度,以帮助数据库系统进行查询优化成本估算。它首先检查表是否处于升级模式,如果是,则直接返回0。然后,它根据表的持久性属性号来决定是获取全局临时表的属性统计信息还是表的属性统计信息。最后,它返回属性的平均宽度,如果没有可用的统计信息,则返回0。其函数源码如下:(路径:src/common/backend/utils/cache/lsyscache.cpp

    /*
     * get_attavgwidth
     * 给定表和属性号,获取该列中条目的平均宽度。如果没有可用数据,则返回零。
     *
     * 当前只用于单个表,不用于继承树,因此不需要 "inh" 参数。
     * 在这一点上调用挂接看起来有些奇怪,但是因为优化器调用这个函数而没有其他方式让插件控制结果,所以需要挂接。
     */
    int32 get_attavgwidth(Oid relid, AttrNumber attnum, bool ispartition)
    {
        HeapTuple tp;
        int32 stawidth;
        char stakind = ispartition ? STARELKIND_PARTITION : STARELKIND_CLASS;
    
        // 如果处于升级模式,返回0
        if (u_sess->attr.attr_common.upgrade_mode != 0)
            return 0;
    
        // 如果不是分区表并且表的持久性是全局临时表,则获取全局临时表的属性统计信息
        if (!ispartition && get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP) {
            tp = get_gtt_att_statistic(relid, attnum);
            if (!HeapTupleIsValid(tp)) {
                return 0;
            }
            stawidth = ((Form_pg_statistic)GETSTRUCT(tp))->stawidth;
            if (stawidth > 0) {
                return stawidth;
            } else {
                return 0;
            }
        }
    
        // 否则,获取表的属性统计信息
        tp = SearchSysCache4(
            STATRELKINDATTINH, ObjectIdGetDatum(relid), CharGetDatum(stakind), Int16GetDatum(attnum), BoolGetDatum(false));
    
        if (HeapTupleIsValid(tp)) {
            stawidth = ((Form_pg_statistic)GETSTRUCT(tp))->stawidth;
            ReleaseSysCache(tp);
            if (stawidth > 0)
                return stawidth;
        }
    
        // 如果没有有效的统计信息,返回0
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    get_typavgwidth 函数

      get_typavgwidth 函数用于估算指定数据类型的平均宽度,以帮助规划器进行查询计划成本估算。如果数据类型是固定宽度的(typlen > 0),则直接返回该类型的长度。如果数据类型不是固定宽度的,根据最大宽度maxwidth)和数据类型来猜测典型数据宽度。如果不知道最大宽度,函数采用一个默认的猜测值。这有助于规划器在不知道确切宽度的情况下做出估算,以便更好地选择查询执行计划。

    /*
     * get_typavgwidth
     * 给定类型 OID 和类型修饰(typmod)值(如果不知道 typmod,则传递 -1),
     * 估算该类型值的平均宽度。这用于规划器(planner),
     * 不需要绝对正确的结果;如果我们不确定,猜测也可以。
     */
    int32 get_typavgwidth(Oid typid, int32 typmod)
    {
        int typlen = get_typlen(typid); // 获取类型的长度(字节数)
        int32 maxwidth;
    
        /*
         * 如果是固定宽度类型,直接返回类型的长度
         */
        if (typlen > 0) {
            return typlen;
        }
    
        /*
         * type_maximum_size 知道某些数据类型的 typmod 的编码;
         * 不要在这里重复这个知识。
         */
        maxwidth = type_maximum_size(typid, typmod);
    
        if (maxwidth > 0) {
            /*
             * 对于 BPCHAR(定长字符类型),最大宽度也是唯一的宽度。
             * 否则,我们需要根据最大宽度来猜测典型数据宽度。
             * 对于最大宽度,假设有一个百分比的典型数据宽度是合理的。
             */
            if (typid == BPCHAROID) {
                return maxwidth;
            }
            if (maxwidth <= 32) {
                return maxwidth; /* 假设是全宽度 */
            }
            if (maxwidth < 1000) {
                return 32 + (maxwidth - 32) / 2; /* 假设是 50% */
            }
            /*
             * 超过1000后,假设我们正在处理类似 "varchar(10000)" 这样的类型,
             * 其限制并不经常达到,因此使用一个固定的估计值。
             */
            return 32 + (1000 - 32) / 2;
        }
    
        /*
         * 如果不知道最大宽度,采用猜测。
         */
        return 32;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    vac_update_relstats 函数

      函数 vac_update_relstats 用于在执行 VACUUMANALYZE 操作时,更新一个关系(表或索引)的统计信息,以确保查询优化和成本估算的准确性。函数的核心功能是通过修改 pg_class 表中的相应条目,更新关系的各种统计信息,包括页数元组数量可见页数是否有索引等。这些统计信息对于数据库系统的性能优化和查询计划非常重要。其函数源码如下所示:(路径:src/gausskernel/optimizer/commands/vacuum.cpp

    /*
     * vac_update_relstats() -- 更新一个关系的统计信息
     *
     * 更新保存在 `pg_class` 表中的关系的整体统计信息,这些统计信息会被用于查询优化和成本估算。
     * 如果进行 ANALYZE,还会更新其他统计信息,但总是要更新这些统计信息。
     * 此函数适用于 `pg_class` 表中的索引和堆关系条目。
     *
     * 我们违反了事务语义,通过使用新值覆盖了关系的现有 `pg_class` 元组。这是合理的,因为不管这个事务是否提交,新值都是正确的。
     * 这样做的原因是,如果我们按常规方式更新这些元组,那么对 `pg_class` 自身的 VACUUM 将无法很好地工作 --- 在 VACUUM 周期结束时,
     * `pg_class` 中的大多数元组都将变得过时。当然,这仅适用于固定大小且非 NULL 的列,而这些列确实是如此。
     *
     * 注意另一个假设:不会有两个表的 VACUUM/ANALYZE 同时运行,也不会有 VACUUM/ANALYZE 与添加索引、规则或触发器等模式更改并行运行。
     * 否则,我们对 relhasindex 等的更新可能会覆盖未提交的更新。
     *
     * 另一个采用这种方式的原因是,当我们处于延迟 VACUUM 并且设置了 PROC_IN_VACUUM 时,我们不能进行任何更新 ---
     * `pg_class` 中的某些元组可能会认为它们可以删除 xid = 我们的 xid 的元组。
     * isdirty - 用于标识关系的数据是否发生了更改。
     *
     * 此函数由 VACUUM 和 ANALYZE 共享。
     */
    void vac_update_relstats(Relation relation, Relation classRel, RelPageType num_pages, double num_tuples,
        BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid)
    {
        Oid relid = RelationGetRelid(relation);
        HeapTuple ctup;
        HeapTuple nctup = NULL;
        Form_pg_class pgcform;
        bool dirty = false;
        bool isNull = false;
        TransactionId relfrozenxid;
        Datum xid64datum;
        bool isGtt = false;
    
        /* 对于全局临时表,将 relstats 保存到 localhash 和 rel->rd_rel,而不是 catalog 中 */
        if (RELATION_IS_GLOBAL_TEMP(relation)) {
            isGtt = true;
            up_gtt_relstats(relation,
                            static_cast<unsigned int>(num_pages), num_tuples,
                            num_all_visible_pages,
                            frozenxid);
        }
    
        /* 获取要修改的元组的副本 */
        ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(ctup))
            ereport(ERROR,
                (errcode(ERRCODE_NO_DATA_FOUND), errmsg("在进行 VACUUM 期间,relid %u 的 pg_class 条目已经消失", relid)));
        pgcform = (Form_pg_class)GETSTRUCT(ctup);
    
        /* 对复制的元组应用必需的更新,如果有的话 */
        dirty = false;
    #ifdef PGXC
        // frozenxid == BootstrapTransactionId 表示是由 execRemote.cpp:ReceivePageAndTuple() 调用
        if (IS_PGXC_DATANODE || (frozenxid == BootstrapTransactionId) || IsSystemRelation(relation)) {
    #endif
            if (isGtt) {
                relation->rd_rel->relpages = (int32) num_pages;
                relation->rd_rel->reltuples = (float4) num_tuples;
                relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
            } else {
                if (pgcform->relpages - num_pages != 0) {
                    pgcform->relpages = num_pages;
                    dirty = true;
                }
                if (pgcform->reltuples - num_tuples != 0) {
                    pgcform->reltuples = num_tuples;
                    dirty = true;
                }
                if (pgcform->relallvisible != (int32)num_all_visible_pages) {
                    pgcform->relallvisible = (int32)num_all_visible_pages;
                    dirty = true;
                }
            }
    #ifdef PGXC
        }
    #endif
        if (pgcform->relhasindex != hasindex) {
            pgcform->relhasindex = hasindex;
            dirty = true;
        }
    
        /*
         * 如果我们发现没有索引,那么也没有主键。这可能需要更加彻底的处理...
         */
        if (pgcform->relhaspkey && !hasindex) {
            pgcform->relhaspkey = false;
            dirty = true;
        }
    
        /* 如果需要,我们还会清除 relhasrules 和 relhastriggers */
        if (pgcform->relhasrules && relation->rd_rules == NULL) {
            pgcform->relhasrules = false;
            dirty = true;
        }
        if (pgcform->relhastriggers && relation->trigdesc == NULL) {
            pgcform->relhastriggers = false;
            dirty = true;
        }
    
        /*
         * relfrozenxid 不应该回退,除非在 PGXC 中,当 xid 与 gxid 不同步且我们希望使用独立后台进程来纠正时。
         * 调用方可以传递 InvalidTransactionId,如果没有新数据。
         */
        xid64datum = tableam_tops_tuple_getattr(ctup, Anum_pg_class_relfrozenxid64, RelationGetDescr(classRel), &isNull);
    
        if (isNull) {
            relfrozenxid = pgcform->relfrozenxid;
    
            if (TransactionIdPrecedes(t_thrd.xact_cxt.ShmemVariableCache->nextXid, relfrozenxid) ||
                !TransactionIdIsNormal(relfrozenxid))
                relfrozenxid = FirstNormalTransactionId;
        } else {
            relfrozenxid = DatumGetTransactionId(xid64datum);
        }
    
        if (TransactionIdIsNormal(frozenxid) && (TransactionIdPrecedes(relfrozenxid, frozenxid)
    #ifdef PGXC
                                                    || !IsPostmasterEnvironment)
    #endif
        ) {
    
            Datum values[Natts_pg_class];
            bool nulls[Natts_pg_class];
            bool replaces[Natts_pg_class];
            errno_t rc;
    
            pgcform->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
    
            rc = memset_s(values, sizeof(values), 0, sizeof(values));
            securec_check(rc, "", "");
            rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
            securec_check(rc, "", "");
            rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
            securec_check(rc, "", "");
    
            replaces[Anum_pg_class_relfrozenxid64 - 1] = true;
            values[Anum_pg_class_relfrozenxid64 - 1] = TransactionIdGetDatum(frozenxid);
    
            nctup = (HeapTuple) tableam_tops_modify_tuple(ctup, RelationGetDescr(classRel), values, nulls, replaces);
            ctup = nctup;
            dirty = true;
        }
    
        /* 如果有任何更改,将元组写入 */
        if (dirty) {
            if (isNull && nctup) {
                simple_heap_update(classRel, &ctup->t_self, ctup);
                CatalogUpdateIndexes(classRel, ctup);
            } else
                heap_inplace_update(classRel, ctup);
    
            if (nctup)
                heap_freetuple(nctup);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155

    测试案例

    1. 创建列存储表,执行以下 SQL

    postgres=# create table t2 (id int) with (orientation=column);
    CREATE TABLE
    postgres=# insert into t2 values (generate_series(1,10));
    INSERT 0 10
    postgres=# analyze t2;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. 步入 estimate_cstore_blocks 函数

    调试信息如下:

    1. 首先检查是否为分区表,gdb调试信息如下:
    bool isPartition = RelationIsPartition(rel); 
    ---------------------------------------------
    (gdb) p isPartition
    $1 = false
    
    • 1
    • 2
    • 3
    • 4
    1. 检查属性是否被删除,gdb调试信息如下:
    if (att->attisdropped)
        continue;
    ---------------------------------------------
    (gdb) p isPartition
    (gdb) p att->attisdropped
    $2 = false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 获取属性类型宽度:
    item_width = get_typlen(att->atttypid);
    ---------------------------------------------
    (gdb) p item_width
    $3 = 4
    
    • 1
    • 2
    • 3
    • 4
    1. 根据判断是否为 DFS 表来计算总页数 total_pages
    if (dfsStore)
       total_pages = ceil((totalTuples * tuple_width * ESTIMATE_BLOCK_FACTOR) / BLCKSZ);
    else
        total_pages = ceil(totalTuples / RelDefaultFullCuSize) * (RelDefaultFullCuSize * tuple_width / BLCKSZ);
    ---------------------------------------------
    (gdb) p total_pages
    $4 = 29
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 根据判断 totalTuplestotal_pages 的大小来为总页数赋值:
    if (totalTuples > 0 && totalTuples < total_pages)
        total_pages = totalTuples;
    
    if (totalTuples > 0 && total_pages <= 0)
        total_pages = 1;
    ---------------------------------------------
    (gdb) p total_pages
    $4 = 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    以上两段代码含义如下:

    1. 如果 totalTuples(实际数据记录数)小于 total_pages(计算出的页数),这可能会导致问题,因为没有足够的数据来填充所有这些页,这会浪费存储空间。这样做可以确保不会浪费额外的页用于存储数据,因为页的数量应该与数据的数量相匹配。
    2. 是为了处理可能的特殊情况,当总元组数大于0但计算出的总页数小于等于0时将总页数设置为1。这可以确保总页数至少为1,以便能够存储数据。当总元组数非零但由于某些原因总页数计算为非正数时,将其设置为1是一个常见的做法,以确保有一个最小的页来存储数据

    以上有关列存更新 pages 的数据已经得到了。

  • 相关阅读:
    Spring 面试题
    Spring框架详解
    【Java|golang】775. 全局倒置与局部倒置
    java反序列化CC1
    js中高级部分知识点总结第二篇
    如何向顶点着色器传递数据
    C#三层架构
    study_notebook
    Vulntarget-a靶场实战记录
    Data-free NAS
  • 原文地址:https://blog.csdn.net/qq_43899283/article/details/134008660