【 OpenGauss源码学习 —— 列存储(analyze)(二)】

news/2024/7/9 20:09:11 标签: postgresql, gaussdb, 数据库

列存储(analyze)

  • 概述
  • analyze_get_relation 函数
    • VacuumStmt 结构体
    • Relation 结构体
    • 代码段解读
    • try_relation_open 函数
    • ConditionalLockRelationOid 函数
  • analyze_rel_internal 函数
    • BufferAccessStrategy 结构体
    • GBLSTAT_HDFS_SAMPLE_ROWS 结构体
    • do_analyze_rel 函数

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

概述

  在OpenGauss中,“ANALYZE” 是一个用于数据库优化的关键操作,它用于收集表中统计信息,以便优化查询性能。对于列存储表ANALYZE 功能的作用是分析表中的数据分布值的密度其他统计信息以便查询优化器能够更好地决定执行计划,从而提高查询的执行效率。其相关知识在【 OpenGauss源码学习 —— 列存储(analyze)(一)】中进行了大致描述,在先前的章节中,我们大致了解了 vacuum 函数的相关操作。其中,函数 analyze_rel 函数用于执行分析操作ANALYZE)或者清理操作VACUUM)的入口函数,它接收一个关系(表)的对象标识符(OID)、分析或清理的语句信息、以及缓冲区访问策略作为参数,然后根据这些参数执行相应的操作
  我们在前一章中已经大致介绍了 analyze_rel 函数,其中 analyze_get_relationanalyze_rel_internal 函数分别负责打开指定的关系(表),以便进行分析操作执行实际的表分析操作,包括收集统计信息、更新系统表等在本文中,我们重点来学习这两个函数。

analyze_get_relation 函数

  analyze_get_relation 函数是用于在进行分析操作analyzevacuum)之前,打开要分析的关系(表),并获取适当的锁确保分析的正确执行

  1. 入参:
  • Oid relid:表示待获取的关系的对象ID。这个参数指定了要获取哪个具体的关系。
  • VacuumStmt* vacstmt:这是一个指向 VacuumStmt 结构的指针,用于表示执行 ANALYZEVACUUM 命令的上下文。VacuumStmt 结构中包含了执行命令的相关信息,例如要分析的关系、分析选项等。
  1. 返回值:
  • Relation 类型,表示获取到的关系的 RelationData 结构。这个结构包含了关于关系的元数据和缓存信息,供后续操作使用。

  analyze_get_relation 函数源码如下:(路径:src/gausskernel/optimizer/commands/analyze.cpp

/*
 * analyze_get_relation() -- get one relation by relid before do analyze.
 * 从 relid 获取一个关系(表),用于在执行分析之前。
 * @in relid - the relation id for analyze or vacuum
 * @in vacstmt - the statment for analyze or vacuum command
 */
Relation analyze_get_relation(Oid relid, VacuumStmt* vacstmt)
{
	// 存储获取到的关系的 RelationData 结构
    Relation onerel = NULL;
    // 表示是否需要获取锁
    bool GetLock = false;
    // 声明一个名为 lockmode 的变量,用于存储获取锁的模式。
    // 该行代码使用三元条件运算符根据 NEED_EST_TOTAL_ROWS_DN(vacstmt) 的结果来确定锁的模式。
    // 如果 NEED_EST_TOTAL_ROWS_DN(vacstmt) 为真,表示需要获取共享锁(AccessShareLock),否则获取排他锁(ShareUpdateExclusiveLock)。
    LOCKMODE lockmode = NEED_EST_TOTAL_ROWS_DN(vacstmt) ? AccessShareLock : ShareUpdateExclusiveLock;

    /*
     * Check for user-requested abort.
     * 检查是否有用户请求的中断信号。
     */
    CHECK_FOR_INTERRUPTS();

    /*
     * Open the relation, getting ShareUpdateExclusiveLock to ensure that two
     * ANALYZEs don't run on it concurrently.  (This also locks out a
     * concurrent VACUUM, which doesn't matter much at the moment but might
     * matter if we ever try to accumulate stats on dead tuples.)
     * 打开关系(表),获取 ShareUpdateExclusiveLock 锁,以确保不会并发运行两个 ANALYZE 操作。
     * 这也会锁定并发的 VACUUM 操作,虽然目前不是很重要,但在将来可能会在死元组上累积统计信息时变得重要。
     * 如果关系已被删除,我们无需处理它。
     */
    if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&
        !(vacstmt->options & VACOPT_NOWAIT)) {
        onerel = try_relation_open(relid, lockmode);
        GetLock = true;
    } else if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&
               ConditionalLockRelationOid(relid, lockmode)) {
        onerel = try_relation_open(relid, NoLock);
        GetLock = true;
    }

    if (!GetLock) {
        onerel = NULL;
        if (IsAutoVacuumWorkerProcess() && u_sess->attr.attr_storage.Log_autovacuum_min_duration >= 0)
            ereport(LOG,
                (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
                    errmsg("skipping analyze of \"%s\" --- lock not available", vacstmt->relation->relname)));
    }

    return onerel;
}

VacuumStmt 结构体

  VacuumStmt 结构体是在 PostgreSQL 数据库中用于表示 VACUUMANALYZE 命令的数据结构。它在数据库源代码中定义,用于存储 VACUUMANALYZE 命令的各种参数和选项。以下是 VacuumStmt 结构体的定义:(路径:src/include/nodes/parsenodes.h

typedef struct VacuumStmt {
    NodeTag type;            /* 结构体类型标签 */
    int options;             /* VacuumOption 标志的按位 OR */
    int flags;               /* 用于区分分区或 B 树的标志 */
                             /* 这些标志的值在 vacuum.h 中定义 */
    Oid rely_oid;            /* 对于 B 树,是堆 B 树的 OID,否则为 InvalidOid */
    int freeze_min_age;      /* 最小冻结年龄,-1 表示使用默认值 */
    int freeze_table_age;    /* 扫描整个表的年龄 */
    RangeVar* relation;      /* 要处理的单个表,或者为 NULL */
    List* va_cols;           /* 列名列表,为 NIL 表示所有列 */

    Relation onepartrel;     /* 用于跟踪已打开的关系 */
    Partition onepart;       /* 用于跟踪已打开的分区 */
    List* partList;          /* 分区列表 */
#ifdef PGXC
    void* HDFSDnWorkFlow;    /* @hdfs HDFSDnWorkFlow 存储分析操作相关信息 */
#endif
    bool isForeignTables;     /* @hdfs 当运行 "analyze [verbose] foreign table;" 命令时为 true */
    bool isPgFdwForeignTables; /* 当外部表的 fdw 是 gc_fdw 时为 true */
#ifdef ENABLE_MOT
    bool isMOTForeignTable;   /* 当前是否是 MOT 外部表 */
#endif

    /*
     * @hdfs
     * 参数 totalFileCnt 和 nodeNo 是由 CNSchedulingForAnalyze 设置的
     * CNSchedulingForAnalyze(
     *     int *totalFilesCnt,
     *     int *nodeNo,
     *     Oid foreignTableId)
     */
    unsigned int totalFileCnt; /* @hdfs 分析外部表操作中要采样的文件数 */
    int nodeNo;                /* @hdfs 哪个数据节点将执行分析操作,
                                  @global 统计:其他协调器将从哪个协调器节点获取统计信息。 */

    /*
     * @hdfs 数据节点总数,我们使用这个数字来调整存储在 pg_class 中的 reltuples 数量
     * 例如:我们执行操作 "analyze tablename",有 x 个数据节点,而 tablename 是一个 HDFS 外部表。
     * 数据节点完成分析命令,协调器从数据节点获取元组数信息。这个数是总元组数的 1/x。在协调器中将这个数值调整为真实值。
     */
    unsigned int DnCnt;

    /*
     * 添加全局统计的参数。
     */
    DestReceiver* dest;       /* 用于数据节点将样本行发送到协调器。 */
    int num_samples;          /* 从数据节点接收的样本行数。 */
    HeapTuple* sampleRows;    /* 从数据节点接收的样本行。 */
    TupleDesc tupleDesc;      /* 普通表的样本行的元组描述符。 */
    int tableidx;             /* 设置当前需要设置样本率或总行数的表索引 */
    GlobalStatInfoEx pstGlobalStatEx[ANALYZE_MODE_MAX_NUM - 1]; /* 全局统计的辅助信息,扩展以识别 HDFS 表。 */
    unsigned int orgCnNodeNo; /* 标识哪个协调器从客户端接收分析命令,其他协调器需要从它获取统计信息。 */
    List* hdfsforeignMapDnList; /* 标识属于分片映射的一些数据节点,用于协调器从它们获取总 reltuples。 */
    bool sampleTableRequired;   /* 需要样本表以获取统计信息。 */
    List* tmpSampleTblNameList; /* 在调试期间识别样本表名称。 */
    bool isAnalyzeTmpTable;     /* 如果分析的表是临时表,则为 true。 */
#ifdef PGXC
    DistributionType disttype; /* 分析表的分布类型。 */
#endif
    AdaptMem memUsage;         /* 分配给语句的自适应内存 */
    Oid curVerifyRel;          /* 当前的关系用于数据库模式以发送远程查询 */
    bool isCascade;            /* 用于验证表 */
} VacuumStmt;

Relation 结构体

  RelationData 结构体包含了关系的各种属性,如物理标识符、缓存信息、元组描述、索引信息、触发器信息等。这个数据结构在数据库系统中用于管理关系的元数据和缓存信息,以提高查询性能和管理操作的效率。下面详细解释一下 RelationData 数据结构的作用:

  1. 元数据存储 RelationData 存储了关系的元数据,如物理标识符、元组描述、关系的对象ID等。这些信息对于数据库的正常操作是必需的,它们被用于查询优化访问控制数据完整性维护等。
  2. 缓存管理关系的部分数据被缓存在 RelationData 中,以提高查询性能。例如,索引元组、触发器信息等可能被缓存在这个数据结构中,避免了频繁的磁盘访问。
  3. 查询优化关系的统计信息、索引信息等可以帮助查询优化器选择最优的查询计划。RelationData 中的信息可以帮助数据库系统评估不同查询计划的代价,并选择最佳的执行路径。
  4. 元数据操作数据库系统需要在运行时处理与关系相关的操作,如表的创建、删除、修改等。RelationData 中的元数据信息可以帮助数据库系统执行这些操作,确保数据的一致性完整性
  5. 缓存管理与复用数据库系统会缓存 RelationData 数据结构,避免了频繁的元数据查询和磁盘访问。这样可以减少系统开销,提高操作的效率。
  6. 触发器和约束管理 RelationData 存储了关于触发器、约束和重写规则的信息,这些信息在数据修改时起着关键作用。例如,插入、更新或删除数据时,系统需要检查关联的触发器和约束,确保数据的一致性。
  7. 统计信息收集数据库系统需要收集关于关系的统计信息,以便查询优化器进行成本估算和执行计划选择。这些统计信息可以存储在 RelationData 中,供系统使用。
  8. 分布式数据库管理 对于分布式数据库系统,RelationData 可以包含与分布式定位、切片映射等相关的信息。这有助于系统进行查询路由和数据分布。

  Relation 结构体函数源码如下:(路径:src/include/utils/rel.h

typedef struct RelationData* Relation;

typedef struct RelationData {

    RelFileNode rd_node; // 关系的物理标识符
    struct SMgrRelationData* rd_smgr; // 缓存的文件句柄,或者为NULL
    int rd_refcnt; // 引用计数
    BackendId rd_backend; // 拥有该临时关系的后端ID
    bool rd_isscannable; // 关系是否可被扫描
    bool rd_isnailed; // 关系是否固定在缓存中
    bool rd_isvalid; // 关系缓存条目是否有效
    char rd_indexvalid; // rd_indexlist的状态: 0 = 无效, 1 = 有效, 2 = 临时强制
    bool rd_islocaltemp; // 关系是否是本会话的临时关系

    SubTransactionId rd_createSubid; // 关系在当前事务中被创建的最高子事务ID
    SubTransactionId rd_newRelfilenodeSubid; // 关系文件节点变更在当前事务中生存的最高子事务ID

    Form_pg_class rd_rel; // RELATION元组
    TupleDesc rd_att; // 元组描述
    Oid rd_id; // 关系的对象ID

    LockInfoData rd_lockInfo; // 锁管理器用于锁定关系的信息
    RuleLock* rd_rules; // 重写规则
    MemoryContext rd_rulescxt; // rd_rules的私有内存上下文,如果有的话
    TriggerDesc* trigdesc; // 触发器信息,如果关系没有触发器则为NULL
    struct RlsPoliciesDesc* rd_rlsdesc; // 行级安全策略,如果没有则为NULL
    List* rd_indexlist; // 关系上的索引OID列表
    Oid rd_oidindex; // 唯一索引的OID,如果有的话
    Oid rd_refSynOid; // 映射关系的参考同义词OID,如果有的话

    Bitmapset* rd_indexattr; // 用于标识在索引中使用的列
    Bitmapset* rd_idattr; // 在复制标识索引中的列

    Oid rd_replidindex; // 关系的复制标识索引的OID,只有在调用RelationGetIndexList/rd_indexvalid > 0时才会设置

    bytea* rd_options; // 解析后的pg_class.reloptions

    Oid rd_partHeapOid; // 分区索引的分区OID
    Form_pg_index rd_index; // 描述该索引的pg_index元组
    struct HeapTupleData* rd_indextuple; // 所有pg_index元组
    Form_pg_am rd_am; // 用于索引的pg_am元组
    int rd_indnkeyatts; // 索引关系的索引键数量
    TableAmType rd_tam_type; // 表访问器方法类型

    MemoryContext rd_indexcxt; // 用于这些信息的私有内存上下文
    RelationAmInfo* rd_aminfo; // 在pg_am中找到的函数的查找信息
    Oid* rd_opfamily; // 每个索引列的操作族OID
    Oid* rd_opcintype; // 每个操作类声明的输入数据类型的OID
    RegProcedure* rd_support; // 支持函数的OID
    FmgrInfo* rd_supportinfo; // 支持函数的查找信息
    int16* rd_indoption; // 每列的AM特定标志
    List* rd_indexprs; // 索引表达式树,如果有的话
    List* rd_indpred; // 索引谓词树,如果有的话
    Oid* rd_exclops; // 排除运算符的OID,如果有的话
    Oid* rd_exclprocs; // 排除运算符的处理函数的OID,如果有的话
    uint16* rd_exclstrats; // 排除运算符的策略编号,如果有的话
    void* rd_amcache; // 索引AM可用的缓存数据
    Oid* rd_indcollation; // 索引的排序规则OID

    struct FdwRoutine* rd_fdwroutine; // 缓存的函数指针,或者为NULL

    Oid rd_toastoid; // 真实TOAST表的OID,或者为InvalidOid
    Oid rd_bucketoid; // 在pg_hashbucket中的bucket OID

    RelationBucketKey* rd_bucketkey; // 指示哪些键用于计算哈希值的bucket键信息

    PartitionMap* partMap; // 分区映射信息
    Oid parentId; // 如果由partitionGetRelation构建,这是分区OID,否则为InvalidOid

    struct PgStat_TableStatus* pgstat_info; // 统计信息收集区域

#ifdef PGXC
    RelationLocInfo* rd_locator_info; // 分布式表定位信息
    PartitionMap* sliceMap; // 切片映射信息
#endif
    Relation parent; // 父关系

    dlist_node node; // 双向链表节点,分区和bucket关系将存储在资源所有者的fakerels列表中

    Oid rd_mlogoid; // mlog的OID
} RelationData;

代码段解读

    /*
     * Open the relation, getting ShareUpdateExclusiveLock to ensure that two
     * ANALYZEs don't run on it concurrently.  (This also locks out a
     * concurrent VACUUM, which doesn't matter much at the moment but might
     * matter if we ever try to accumulate stats on dead tuples.) If the rel
     * has been dropped since we last saw it, we don't need to process it.
     */
    if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&
        !(vacstmt->options & VACOPT_NOWAIT)) {
        onerel = try_relation_open(relid, lockmode);
        GetLock = true;
    } else if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&
               ConditionalLockRelationOid(relid, lockmode)) {
        onerel = try_relation_open(relid, NoLock);
        GetLock = true;
    }

  这段代码是函数 analyze_get_relation 中的一部分,用于根据一些条件获取关系的 RelationData 结构,并根据需要获取锁。以下是逐行解释:

  1. 此行开始一个条件判断块。它首先检查 vacstmt->flags 是否表示需要进行 VACUUM 操作,或者是否需要针对主分区执行 VACUUM 操作。同时,它还检查 vacstmt->options 是否未设置 VACOPT_NOWAIT 标志,以确定是否可以进行等待式的操作。
if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&
    !(vacstmt->options & VACOPT_NOWAIT)) {
  1. 如果上述条件为真,表示满足进行 VACUUM 操作的条件,且不需要立即操作(即允许等待),那么这段代码尝试以指定的锁模式打开关系try_relation_open 是一个函数,用于尝试打开关系并返回关系的 RelationData 结构。同时,将 GetLock 标志设置为 true,表示成功获取锁
onerel = try_relation_open(relid, lockmode);
GetLock = true;
  1. 如果上述条件不满足,这里开始另一个条件判断块。它再次检查是否满足进行 VACUUM 操作的条件,同时尝试使用 ConditionalLockRelationOid 函数以指定的锁模式对关系进行条件性加锁
} else if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&
           ConditionalLockRelationOid(relid, lockmode)) {
  1. 如果上述条件为真,表示虽然无法立即获取锁,但满足进行 VACUUM 操作的条件,那么这段代码尝试以无锁模式打开关系。然后将 GetLock 标志设置为 true,表示成功获取锁。
onerel = try_relation_open(relid, NoLock);
GetLock = true;

  在这段代码中,根据不同的情况,会尝试以不同的锁模式打开关系,从而为后续的操作获取关系的 RelationData 结构

try_relation_open 函数

  try_relation_open 函数的作用是尝试以指定的锁模式打开一个数据库关系(表、索引等)。与普通的 relation_open 不同,try_relation_open 不会在关系不存在时抛出错误,而是返回 NULL。这在一些情况下很有用,例如在执行某些操作之前,需要确认关系是否存在,如果存在则打开关系并执行操作,如果不存在则不进行任何操作
  try_relation_open 函数源码如下:(路径:src/gausskernel/storage/access/heap/heapam.cpp

/* ----------------
 *		try_relation_open - open any relation by relation OID
 *
 *		Same as relation_open, except return NULL instead of failing
 *		if the relation does not exist.
 * ----------------
 */
Relation try_relation_open(Oid relationId, LOCKMODE lockmode)
{
    Relation r;

    Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);

    /* Get the lock first */
    if (lockmode != NoLock) {
        LockRelationOid(relationId, lockmode);
    }

    /*
     * Now that we have the lock, probe to see if the relation really exists
     * or not.
     */
    if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relationId))) {
        /* Release useless lock */
        if (lockmode != NoLock) {
            UnlockRelationOid(relationId, lockmode);
        }

        return NULL;
    }

    /* Should be safe to do a relcache load */
    r = RelationIdGetRelation(relationId);
    if (!RelationIsValid(r)) {
        ereport(ERROR,
                (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("could not open relation with OID %u", relationId)));
    }

    /* Make note that we've accessed a temporary relation */
    if (RelationUsesLocalBuffers(r)) {
        t_thrd.xact_cxt.MyXactAccessedTempRel = true;
    }
    /* Make note that we've accessed a repliacted relation */
    if (r->rd_locator_info != NULL && IsRelationReplicated(r->rd_locator_info)) {
        t_thrd.xact_cxt.MyXactAccessedRepRel = true;
    }
    pgstat_initstats(r);

    return r;
}

ConditionalLockRelationOid 函数

  ConditionalLockRelationOid 函数是用于在非阻塞的情况下尝试获取指定关系的锁,并在成功获取锁时返回 true,否则返回 false。这种非阻塞的锁获取适用于那些不希望等待的场景,以避免出现长时间的阻塞ConditionalLockRelationOid 函数源码如下:(路径:src/gausskernel/storage/lmgr/lmgr.cpp

/*
 *		ConditionalLockRelationOid
 *
 * As above, but only lock if we can get the lock without blocking.
 * Returns TRUE iff the lock was acquired.
 *
 * NOTE: we do not currently need conditional versions of all the
 * LockXXX routines in this file, but they could easily be added if needed.
 */
bool ConditionalLockRelationOid(Oid relid, LOCKMODE lockmode)
{
    LOCKTAG tag;
    LockAcquireResult res;
	
	// 设置锁标签,标识要锁定的关系,这里使用关系的对象标识符作为锁的标签。
    SetLocktagRelationOid(&tag, relid);

	// 尝试获取指定标签和锁模式的锁
    res = LockAcquire(&tag, lockmode, false, true);
    if (res == LOCKACQUIRE_NOT_AVAIL) {
        return false;
    }

    /*
     * Now that we have the lock, check for invalidation messages; see notes
     * in LockRelationOid.
     */
    // 检查锁获取的结果,如果结果是 LOCKACQUIRE_NOT_AVAIL,表示锁不可用,无法立即获取,因此返回 false。
    if (res != LOCKACQUIRE_ALREADY_HELD || u_sess->inval_cxt.deepthInAcceptInvalidationMessage > 0)
        AcceptInvalidationMessages();

    return true;
}

analyze_rel_internal 函数

  analyze_rel_internal 函数用于分析一个关系(表)它接收一个待分析的关系、分析或清理命令的语句信息、缓冲区访问策略、分析类型以及用于 DFS 表、增量表或复杂表的样本行信息作为参数
  换句话说,analyze_rel_internal 函数的主要作用是执行对指定的关系(表或索引)进行分析操作分析操作旨在更新关系的统计信息,以便优化查询执行计划,提高数据库查询性能。通过分析关系的数据分布、数据密度等信息,数据库系统可以更好地选择查询执行计划,从而避免不必要的磁盘访问和资源浪费
  analyze_rel_internal 函数源码如下:(路径:src/gausskernel/optimizer/commands/analyze.cpp

/*
 * Description: 分析单个关系的入口函数。
 *
 * Parameters:
 *  @in onerel: 待分析的关系
 *  @in vacstmt: 分析或清理命令的语句信息
 *  @in bstrategy: 缓冲区访问策略对象
 *  @in analyzemode: 表的分析类型(普通表、DFS 表或增量表)
 *  @in pstHdfsSampleRows: 用于 DFS 表、增量表或复杂表的样本行信息
 */
static void analyze_rel_internal(Relation onerel, VacuumStmt* vacstmt, BufferAccessStrategy bstrategy,
    AnalyzeMode analyzemode, GBLSTAT_HDFS_SAMPLE_ROWS* pstHdfsSampleRows)
{
	// 定义一个函数指针变量,用于指向获取样本行数据的函数
    AcquireSampleRowsFunc acquirefunc = NULL;
    // 初始化错误级别和消息级别
    int elevel;
    int messageLevel;
    // 初始化关系页面数和分区列表
    BlockNumber relpages = 0;
    List* partList = NIL;
    // 根据是否需要估算总行数,选择适当的锁模式
    LOCKMODE lockmode = NEED_EST_TOTAL_ROWS_DN(vacstmt) ? AccessShareLock : ShareUpdateExclusiveLock;

    AssertEreport(onerel, MOD_OPT, "在进行分析时 onerel 不应为 NULL");

    /* 设置静态变量 */
    u_sess->analyze_cxt.vac_strategy = bstrategy;
	
	// 设置日志消息级别为 WARNING
    messageLevel = WARNING;
    // 当需要输出日志消息时,消息的严重程度将设置为 DEBUG2
    elevel = DEBUG2;

    if (vacstmt->options & VACOPT_VERBOSE) {
        messageLevel = VERBOSEMESSAGE;
        elevel = VERBOSEMESSAGE;
    }

    /*
     * 检查权限,这应与 VACUUM 的检查相匹配!
     */
    // 检查当前用户是否具有执行 VACUUM 操作的权限
    AclResult aclresult = pg_class_aclcheck(RelationGetPgClassOid(onerel, false), GetUserId(), ACL_VACUUM);
    // 如果用户没有执行 VACUUM 操作的权限,并且不满足下面几种特殊情况,将输出相应的警告信息
    if (aclresult != ACLCHECK_OK && !(pg_class_ownercheck(RelationGetPgClassOid(onerel, false), GetUserId()) ||
            (pg_database_ownercheck(u_sess->proc_cxt.MyDatabaseId, GetUserId()) && !onerel->rd_rel->relisshared) ||
                (isOperatoradmin(GetUserId()) && u_sess->attr.attr_security.operation_mode))) {
        /* 如果在 VACUUM 过程中已经有相应的警告,无需再次输出 WARNING */
        if (!(vacstmt->options & VACOPT_VACUUM)) {
            if (onerel->rd_rel->relisshared)
                ereport(messageLevel,
                    (errmsg("跳过 \"%s\" --- 只有系统管理员可以对其进行分析", RelationGetRelationName(onerel))));
            else if (onerel->rd_rel->relnamespace == PG_CATALOG_NAMESPACE)
                ereport(messageLevel,
                    (errmsg("跳过 \"%s\" --- 只有系统管理员或数据库所有者可以对其进行分析",
                        RelationGetRelationName(onerel))));
            else
                ereport(messageLevel,
                    (errmsg("跳过 \"%s\" --- 只有表或数据库所有者可以对其进行分析",
                        RelationGetRelationName(onerel))));
        }

        relation_close(onerel, lockmode);

        return;
    }

    /*
     * 静默地忽略其他后端的临时表 --- 对它们进行分析相当无意义,因为它们的内容可能在磁盘上不是最新的。
     * (我们不在此处抛出警告;这只会在数据库范围的 ANALYZE 过程中引起冗余。)
     */
    if (RELATION_IS_OTHER_TEMP(onerel)) {
        relation_close(onerel, lockmode);
        return;
    }

    if (RELATION_IS_GLOBAL_TEMP(onerel) && !gtt_storage_attached(RelationGetRelid(onerel))) {
        relation_close(onerel, ShareUpdateExclusiveLock);
        return;
    }

    /*
     * 我们可以对任何表执行 ANALYZE,除了 pg_statistic。参见 update_attstats
     */
    // 检查当前关系是否是 pg_statistic 表
    if (RelationGetRelid(onerel) == StatisticRelationId) {
    	// 断言 pg_statistic 表不应该是分区表
        AssertEreport(RelationIsNonpartitioned(onerel), MOD_OPT, "pg_statistic 不应为分区表。");

        if (!IsInitdb && !IS_SINGLE_NODE) {
            elog(WARNING, "系统目录 pg_statistic 不能进行分析,将跳过。");
        }

		// 关闭当前关系并释放对应的锁,然后返回
        relation_close(onerel, lockmode);
        return;
    }

    /*
     * 检查是否为普通表或外部表;我们以前在 get_rel_oids() 中进行了此检查,但在锁定关系之后检查似乎更安全。
     */
    if (onerel->rd_rel->relkind == RELKIND_RELATION ||
        onerel->rd_rel->relkind == RELKIND_MATVIEW) {
        /* 普通表,所以我们将使用常规的行获取函数 */
        /* 还会获取普通表的大小 */
        if (RelationIsPartitioned(onerel)) {
            Partition part = NULL;
            ListCell* partCell = NULL;

            partList = relationGetPartitionList(onerel, lockmode);

            foreach (partCell, partList) {
                part = (Partition)lfirst(partCell);
                relpages += PartitionGetNumberOfBlocks(onerel, part);
            }
        } else {
            relpages = RelationGetNumberOfBlocks(onerel);
        }
    } else if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE 
               || onerel->rd_rel->relkind == RELKIND_STREAM) {
        /*
         * @hdfs
         * 对于外部表,调用 FDW 的钩子函数来检查是否支持分析。
         */
        bool retValue = false;
        FdwRoutine* fdwroutine = GetFdwRoutineForRelation(onerel, false);

        /* 是否支持分析操作 */
        if (NULL != fdwroutine->AnalyzeForeignTable) {
            /* 是否实现了 GetFdwType 接口,以及文件类型是否为 HDFS_ORC */
            if (isObsOrHdfsTableFormTblOid(RelationGetRelid(onerel)) ||
                (IS_OBS_CSV_TXT_FOREIGN_TABLE(RelationGetRelid(onerel)) && !isWriteOnlyFt(RelationGetRelid(onerel)))) {
                /* 传递 AnalyzeForeignTable 所需的数据 */
                retValue = fdwroutine->AnalyzeForeignTable(
                    onerel, &acquirefunc, &relpages, (void*)vacstmt->HDFSDnWorkFlow, false);
            } else {
                /* 其他类型的外部表 */
                retValue = fdwroutine->AnalyzeForeignTable(onerel, &acquirefunc, &relpages, 0, false);
            }

            if (!retValue) {
                /* 对于 mysql_fdw,抑制警告信息 */
                messageLevel = isMysqlFDWFromTblOid(RelationGetRelid(onerel)) ? LOG : messageLevel;
                ereport(messageLevel,
                    (errmsg(
                        "跳过 \"%s\" --- 无法对该外部表进行分析。", RelationGetRelationName(onerel))));
                relation_close(onerel, lockmode);
                return;
            }

        } else {
            ereport(messageLevel,
                (errmsg("表 %s 不支持分析操作。", RelationGetRelationName(onerel))));
            relation_close(onerel, lockmode);
            return;
        }
    } else {
        /* 如果在 VACUUM 过程中已经有相应的警告,无需再次输出 WARNING */
        if (!(vacstmt->options & VACOPT_VACUUM))
            ereport(messageLevel,
                (errmsg("跳过 \"%s\" --- 无法分析非表或特殊系统表",
                    RelationGetRelationName(onerel))));

        if (RelationIsPartitioned(onerel)) {
            releasePartitionList(onerel, &partList, lockmode);
        }

        relation_close(onerel, lockmode);
        return;
    }

    /*
     * 好了,开始分析。首先告诉其他后端我在进行 ANALYZE。
     */
    LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
    t_thrd.pgxact->vacuumFlags |= PROC_IN_ANALYZE;
    LWLockRelease(ProcArrayLock);

    /* 同时获取普通表的大小 */
    if (RelationIsPartitioned(onerel)) {
        vacstmt->partList = partList;
    }

    /*
     * 执行常规的非递归 ANALYZE。
     */
    do_analyze_rel(onerel, vacstmt, relpages, false, elevel, analyzemode, pstHdfsSampleRows);

    /*
     * 如果有子表,则执行递归的 ANALYZE。
     */
    if (RelationIsPAXFormat(onerel))
        do_analyze_rel(onerel, vacstmt, relpages, true, elevel, ANALYZECOMPLEX, pstHdfsSampleRows);

    /*
     * 现在关闭源关系,但保留锁,以便在提交之前没有人删除它。
     * (如果有人这样做,他们将无法清除我们在 pg_statistic 中创建的条目。
     * 此外,在提交之前释放锁会使我们暴露于 update_attstats 中的并发更新失败。)
     */
    if (RelationIsPartitioned(onerel)) {
        releasePartitionList(onerel, &partList, NoLock);
    }

    relation_close(onerel, NoLock);

    /*
     * 重置我的 PGXACT 标志。注意:我们需要在此处进行,而不是在 vacuum_rel 中,因为 end-of-xact 代码会清除 vacuum 标志。
     */
    LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
    t_thrd.pgxact->vacuumFlags &= ~PROC_IN_ANALYZE;
    LWLockRelease(ProcArrayLock);
}

函数 analyze_rel_internal 的入参解释如下:

  1. Relation onerel: 要进行统计分析的目标关系(表或索引)的 Relation 结构。它包含了关于该关系的元数据和状态信息。
  2. VacuumStmt* vacstmt: VACUUMANALYZE 命令的信息和选项。它是对 VACUUMANALYZE 命令的解析和处理的结果,包含了执行这些操作所需的信息。
  3. BufferAccessStrategy bstrategy: 缓冲区访问策略对象,用于执行缓冲区管理相关操作,如预读取数据块。
  4. AnalyzeMode analyzemode: 统计分析模式,表示当前是进行正常的统计分析还是其他特定模式,比如针对列存表的特殊分析。源码定义如下:(路径:src/include/nodes/parsenodes.h
/*
 * Currently, the HDFS table need collect three statistics information
 * in pg_statistic. we define AnalyzeMode enum strunct to realize global
 * analyze.
 * ANALYZENORMAL:普通的分析模式,执行常规的分析命令。
 * ANALYZEMAIN:主要模式,在执行全局分析时,仅收集 HDFS 表的统计信息。
 * ANALYZEDELTA:增量模式,在执行全局分析时,仅收集 Delta 表的统计信息。
 * ANALYZECOMPLEX:复合模式,在执行全局分析时,同时收集 HDFS 表和 Delta 表的统计信息。
 */
typedef enum AnalyzeMode { ANALYZENORMAL = 0, ANALYZEMAIN = 1, ANALYZEDELTA = 2, ANALYZECOMPLEX = 3 } AnalyzeMode;
  1. GBLSTAT_HDFS_SAMPLE_ROWS* pstHdfsSampleRows: 一个结构体指针,用于保存特定于列存表分析的信息。在正常情况下,对于非列存表的分析,该参数通常为 NULL。

BufferAccessStrategy 结构体

  BufferAccessStrategy 结构体用于管理一个循环的共享缓冲区,以便进行重用。这个结构体中存储了私有(非共享)状态,用于实现缓冲区访问策略。函数源码如下:(路径:src/include/storage/buf/bufmgr.h

/*
 * Buffer access strategy objects.
 */
typedef struct BufferAccessStrategyData* BufferAccessStrategy;


/*
 * Private (non-shared) state for managing a ring of shared buffers to re-use.
 * This is currently the only kind of BufferAccessStrategy object, but someday
 * we might have more kinds.
 */
typedef struct BufferAccessStrategyData {
    /* Overall strategy type */
    // 缓冲区访问策略的类型
    BufferAccessStrategyType btype;
    /* Number of elements in buffers[] array */
    // 循环缓冲区中的槽数量
    int ring_size;

    /*
     * Index of the "current" slot in the ring, ie, the one most recently
     * returned by GetBufferFromRing.
     */
    // 当前槽位在循环缓冲区中的索引,即最近由 GetBufferFromRing 返回的槽位
    int current;

    /* Number of elements to flush behind current */
    // 刷新当前槽位后面的槽位数量
    int flush_rate;

    /*
     * True if the buffer just returned by StrategyGetBuffer had been in the
     * ring already.
     */
    // 一个标志位,表示最近由 StrategyGetBuffer 返回的缓冲区是否已经存在于循环缓冲区中
    bool current_was_in_ring;

    /*
     * Array of buffer numbers.  InvalidBuffer (that is, zero) indicates we
     * have not yet selected a buffer for this ring slot.  For allocation
     * simplicity this is palloc'd together with the fixed fields of the
     * struct.
     */
    // 一个数组,存储着缓冲区的编号
    Buffer buffers[FLEXIBLE_ARRAY_MEMBER]; /* VARIABLE SIZE ARRAY */
} BufferAccessStrategyData;

GBLSTAT_HDFS_SAMPLE_ROWS 结构体

  GBLSTAT_HDFS_SAMPLE_ROWS 结构体用于存储全局统计信息中的 HDFS 表的样本行数据。这个结构体的作用是在执行全局统计分析时,收集 HDFS 表的样本数据,以便计算并更新统计信息。
  GBLSTAT_HDFS_SAMPLE_ROWS 结构体的目的是在进行全局统计分析时,有效地管理和存储 HDFS 表的样本数据,以便后续计算和更新全局统计信息。样本数据对于估计表的大小、选择查询计划等都非常重要,因此结构体的设计有助于高效地收集和管理这些关键信息。函数源码如下:(路径:src/include/nodes/parsenodes.h

/* All sample rows of HDFS table for global stats. */
typedef struct {
	// 用于存储样本行数据的内存上下文。在执行全局统计分析时,会在这个上下文中分配内存来存储样本行数据
    MemoryContext hdfs_sample_context;                 /* using to save sample rows. */
    // 总样本行计数,包括 DFS 表和 Delta 表的样本行数量。用于记录样本行的总数,以便后续在计算统计信息时使用。
    double totalSampleRowCnt;                          /* total sample row count include dfs table and delta table */
    // 一个数组,包含了不同模式(包括 HDFS 表和 Delta 表)下的样本行数据。ANALYZECOMPLEX 表示复合模式,其中包含 HDFS 表和 Delta 表的样本数据。
    HDFS_SAMPLE_ROWS stHdfsSampleRows[ANALYZECOMPLEX]; /* sample rows include dfs table and delta table. */
} GBLSTAT_HDFS_SAMPLE_ROWS;

do_analyze_rel 函数

  do_analyze_rel 函数的作用是对给定的表进行分析(analyze)操作。do_analyze_rel 函数是分析过程中的核心部分,负责对指定表进行统计分析,获取样本数据并计算统计信息,然后更新相关的系统表信息。这个函数在分析过程中处理了许多细节,包括以下主要步骤:

  1. 初始化准备 设置函数中需要用到的各种变量,如属性统计信息、索引信息、目标行数等。根据分析模式确定表的索引。
  2. 检查权限 检查当前用户是否有足够的权限对表进行分析。如果没有权限,根据情况输出相应的警告信息。
  3. 排除特殊表 如果正在分析的是统计信息表(pg_statistic),则输出警告并跳过分析,因为统计信息表不应该是分区表。
  4. 获取目标行数 根据分析模式、表的属性信息等,计算需要分析的目标行数。
  5. 获取样本行数据根据目标行数,从表中获取样本行数据用于统计分析。如果需要在数据节点上获取样本行数据,则从数据节点获取。
  6. 计算统计信息对样本行数据进行统计分析,计算各列的统计信息,包括最小值、最大值、均值等。
  7. 更新统计信息 将计算得到的统计信息更新到系统目录表 pg_statistic 中。
  8. 更新 pg_class 信息 更新表的 pg_class 表中的统计信息,包括行数、死行数等。
  9. 报告进度根据需要,向统计收集器报告分析进度。
  10. 清理操作根据分析选项进行必要的清理,关闭索引等。
  11. 记录日志如果自动分析进程启动且满足日志记录条件,则记录分析操作的日志。
  12. 完成分析完成分析操作,包括回收资源和上下文。

  do_analyze_rel 函数源码如下:(路径:src/gausskernel/optimizer/commands/analyze.cpp

入参解释:

  1. Relation onerel:要分析的关系(表)的 Relation 结构体,表示要对哪个关系执行分析操作。
  2. VacuumStmt* vacstmt:包含有关分析的信息和选项的结构体,这些信息包括要分析的对象、分析选项等。
  3. BlockNumber relpages:关系(表)的总块数(页数),表示该表的大小。
  4. bool inh:指示是否对整个继承树进行分析,即是否包括所有子表。
  5. int elevel:用于指定在错误报告和日志消息中使用的错误级别,表示消息的重要性。
  6. AnalyzeMode analyzemode:分析模式的枚举,指示分析的类型,如NORMALMAINDELTA等。
  7. GBLSTAT_HDFS_SAMPLE_ROWS* pstHdfsSampleRows:一个结构体指针,用于保存全局统计信息和样本行数据,特别是用于处理 HDFS 表的情况。
/*
 *	do_analyze_rel() -- analyze one relation, recursively or not
 *
 * Note that "acquirefunc" is only relevant for the non-inherited case.
 * If we supported foreign tables in inheritance trees,
 * acquire_inherited_sample_rows would need to determine the appropriate
 * acquirefunc for each child table.
 */
static void do_analyze_rel(Relation onerel, VacuumStmt* vacstmt, BlockNumber relpages, bool inh, int elevel,
    AnalyzeMode analyzemode, GBLSTAT_HDFS_SAMPLE_ROWS* pstHdfsSampleRows)
{
	// 初始化各种变量和上下文信息
	int attr_cnt = 0;                        // 属性数量初始化为0
	int i = 0;                               // 循环计数器初始化为0
	Relation* Irel = NULL;                   // 索引关系(表)指针数组初始化为NULL
	int nindexes = 0;                        // 索引数量初始化为0
	bool hasindex = false;                   // 是否有索引初始化为false
	VacAttrStats** vacattrstats = NULL;      // 属性统计信息指针数组初始化为NULL
	AnlIndexData* indexdata = NULL;          // 索引统计信息指针初始化为NULL
	int64 numrows = 0;                       // 估计的总行数初始化为0
	int64 targrows = 0;                      // 目标采样行数初始化为0
	double totalrows = 0;                    // 总行数初始化为0
	double totaldeadrows = 0;                // 死亡行数初始化为0
	HeapTuple* rows = NULL;                  // 堆元组指针数组初始化为NULL
	PGRUsage ru0;                            // 进程资源使用情况结构体
	TimestampTz starttime = 0;               // 起始时间初始化为0
	MemoryContext caller_context = NULL;     // 调用者上下文初始化为NULL
	Oid save_userid = 0;                     // 保存用户ID初始化为0
	int save_sec_context = 0;                // 保存安全上下文初始化为0
	int save_nestlevel = 0;                  // 保存嵌套层级初始化为0
	
	// 根据不同的分析模式设置对应的表索引
	int tableidx = (analyzemode == ANALYZENORMAL) ? analyzemode : (analyzemode - 1);
	vacstmt->tableidx = tableidx;

	/*
	 * (1) 针对复制表
	 * 不支持复制表的百分比采样模式,
	 * 而我们在扩展统计信息中需要使用这种模式,
	 * 所以,当检测到扩展统计信息时,我们让数据节点使用百分比采样模式,
	 * 这样协调节点可以从数据节点获取统计信息。
	 *
	 * (2) 检查扩展统计信息的可用性
	 */
	bool replicate_needs_extstats = false; // 标识复制表是否需要扩展统计信息
	// 检查扩展统计信息的可用性
	es_check_availability_for_table(vacstmt, onerel, inh, &replicate_needs_extstats);

    if (inh)
        ereport(elevel,
            (errmsg("analyzing \"%s.%s\" inheritance tree",
                get_namespace_name(RelationGetNamespace(onerel)),
                RelationGetRelationName(onerel))));
    else
        ereport(elevel,
            (errmsg("analyzing \"%s.%s\"",
                get_namespace_name(RelationGetNamespace(onerel)),
                RelationGetRelationName(onerel))));

    caller_context = do_analyze_preprocess(onerel->rd_rel->relowner,     // 当前关系的所有者
	    &ru0,                // 存储资源使用情况信息的结构体
	    &starttime,          // 分析开始时间
	    &save_userid,        // 保存的用户 ID
	    &save_sec_context,   // 保存的安全上下文信息
	    &save_nestlevel,     // 保存的嵌套级别
	    analyzemode,         // 分析模式(NORMAL、MAIN、DELTA、COMPLEX)
	    pstHdfsSampleRows    // HDFS 表的全局样本行信息
);

    /* Ready and construct for all attributes info in order to compute statistic. */
    /* 准备并构建用于计算统计信息的所有属性信息。 */
    vacattrstats =
        get_vacattrstats_by_vacstmt(onerel, vacstmt, &attr_cnt, &nindexes, &indexdata, &hasindex, inh, &Irel);

	/*
	 * 如果没有初始化的 VacAttrStats 实例,停止分析过程。
	 * 这会发生在使用 'analyze t ((a, b))' 收集扩展统计信息时,
	 * 当将 default_statistics_target 设置为正数时。
	 */
	if (attr_cnt <= 0) {
	    // 关闭索引并进行最终处理
	    vac_close_indexes(nindexes, Irel, NoLock);
	
	    // 完成分析的最终处理,包括内存上下文和安全上下文的恢复
	    do_analyze_finalize(caller_context, save_userid, save_sec_context, save_nestlevel, analyzemode);
	
	    // 如果分析模式小于等于 ANALYZEMAIN
	    if (analyzemode <= ANALYZEMAIN) {
	        // 如果 default_statistics_target 大于等于 0
	        if (default_statistics_target >= 0)
	            // 输出提示信息,建议将 default_statistics_target 设置为负值以收集扩展统计信息
	            elog(INFO, "Please set default_statistics_target to a negative value to collect extended statistics.");
	        else
	            // 输出提示信息,指示没有可用于收集统计信息的列
	            elog(INFO, "No columns in %s can be used to collect statistics.", NameStr(onerel->rd_rel->relname));
	    }
	    // 返回,结束分析过程
	    return;
	}

	/*
	 * 确定需要采样的行数,使用所有可分析列中的最差情况。
	 * 我们使用最低为100行,以避免在Vitter算法中可能出现的溢出情况。
	 * (注意:在没有可分析列的情况下,这也将是目标行数。)
	 */
	targrows = 100;
	
	// 对于每个可分析列,找到最小行数的列,并将其作为目标行数
	for (i = 0; i < attr_cnt; i++) {
	    if (targrows < vacattrstats[i]->minrows)
	        targrows = vacattrstats[i]->minrows;
	}
	
	// 对于每个索引,找到其可分析列中最小行数的列,并将其作为目标行数
	for (int ind = 0; ind < nindexes; ind++) {
	    AnlIndexData* thisdata = &indexdata[ind];
	
	    for (i = 0; i < thisdata->attr_cnt; i++) {
	        if (targrows < thisdata->vacattrstats[i]->minrows)
	            targrows = thisdata->vacattrstats[i]->minrows;
	    }
	}

	 /*
	 * 如果表位于系统范围内或者需要从数据节点获取样本行数据
	 */
	if (onerel->rd_id < FirstNormalObjectId || NEED_GET_SAMPLE_ROWS_DN(vacstmt)) {
	    /*
	     * 协调节点已完成样本率计算,数据节点获取总行数和样本。
	     * 如果样本率大于等于0,表示协调节点应从数据节点获取样本行数据。
	     */
	    if (NEED_GET_SAMPLE_ROWS_DN(vacstmt)) {
	        // 判断是否应使用百分比方式计算样本行数
	        bool use_percent = whether_use_percent(vacattrstats, attr_cnt, nindexes, indexdata);
	
	        if (use_percent) {
	            // 获取总行数、样本行数和其他信息
	            rows = get_total_rows<true>(onerel,
	                vacstmt,
	                relpages,
	                inh,
	                elevel,
	                vacattrstats,
	                attr_cnt,
	                targrows,
	                &totalrows,
	                &totaldeadrows,
	                &numrows,
	                pstHdfsSampleRows,
	                analyzemode);
	            // 将总行数存储在vacstmt结构体中的pstGlobalStatEx字段中
	            vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts = totalrows;
	    	}

        // 计算实际的目标行数(targrows)
        targrows =
            get_target_rows(onerel, vacattrstats, attr_cnt, nindexes, indexdata, totalrows, targrows, use_percent);
    	}

        /*
         * 仅为系统表或所有数据节点上的样本率大于1的情况获取目标行数
         */
        rows = get_total_rows<false>(onerel,
            vacstmt,
            relpages,
            inh,
            elevel,
            vacattrstats,
            attr_cnt,
            targrows,
            &totalrows,
            &totaldeadrows,
            &numrows,
            pstHdfsSampleRows,
            analyzemode);
    }

	/*
	 * 如果 sampleRate 为 -1,表示数据节点将估计的总行数发送给协调节点(CN)。
	 */
	if (NEED_EST_TOTAL_ROWS_DN(vacstmt)) {
	    /* 除了 HDFS 和 Delta 复杂模式外,我们应该将估计的总行数发送给协调节点。 */
	    if (analyzemode < ANALYZECOMPLEX) {
	        /* 获取估计的总行数。 */
	        rows = get_total_rows<true>(onerel,
	            vacstmt,
	            relpages,
	            inh,
	            elevel,
	            vacattrstats,
	            attr_cnt,
	            targrows,
	            &totalrows,
	            &totaldeadrows,
	            &numrows,
	            pstHdfsSampleRows,
	            analyzemode);
	
	        // 将估计的总行数保存到 pstGlobalStatEx 结构中
	        vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts = totalrows;
	
	        // 计算内存大小(KB),数据节点需要发送给协调节点
	        vacstmt->pstGlobalStatEx[vacstmt->tableidx].topMemSize =
	            compute_com_size(vacstmt, onerel, vacstmt->tableidx) * GetOneTupleSize(vacstmt, onerel) / 1024;
	
	        // 如果是 ANALYZENORMAL 或 ANALYZEMAIN 模式,发送估计的总行数到协调节点
	        if ((analyzemode == ANALYZENORMAL) || (analyzemode == ANALYZEMAIN)) {
	            send_totalrowcnt_to_cn(vacstmt, analyzemode, totalrows);
	        }
	    }

		/*
		 * 如果不是继承关系(非分区表),关闭索引并进行分析后续处理。
		 */
        if (!inh) {
            vac_close_indexes(nindexes, Irel, NoLock);
        }
		
		// 完成分析后的清理工作
        do_analyze_finalize(caller_context, save_userid, save_sec_context, save_nestlevel, analyzemode);

        return;
    }

    /*
     * We need do analyze to compute statistic for sample rows only on datanode or
     * required sample rows on coordinator.
     *
     * collect extended statistic for replicate table will use 'sampletable' method in data-node
     * 如果需要分析样本行数据,且不是为了收集复制表的扩展统计信息
     */
    if (NEED_ANALYZE_SAMPLEROWS(vacstmt) && (!replicate_needs_extstats)) {
	    /*
	     * 计算统计数据。在计算每列的过程中,临时结果存储在子上下文中。
	     * 计算例程负责确保存储在 VacAttrStats 结构中的任何内容都分配在 u_sess->analyze_cxt.analyze_context 中。
	     */
        bool ret = do_analyze_samplerows(onerel,
            vacstmt,
            attr_cnt,
            vacattrstats,
            hasindex,
            nindexes,
            indexdata,
            inh,
            Irel,
            &totalrows,
            &numrows,
            rows,
            analyzemode,
            pstHdfsSampleRows,
            caller_context,
            save_userid,
            save_sec_context,
            save_nestlevel);
	    /*
	     * 如果在本地分析过程中修改了关系的属性,
	     * 则需要返回,因为 caller_context 已经被 finalize。
	     */
        if (!ret) {
            return;
        }
    } else if (NEED_ANALYZE_SAMPLETABLE(vacstmt) || replicate_needs_extstats) {
        if (vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts > 0) {
            /*
             * There is a concurrency condition:
             * coordinator received estimate totalRowCnts from all datanodes then estimate sample rate
             * for the first step. And there is insert or delete rows concurrency before coordinator
             * received real totalRowCnts from all datanodes.
             * The sampleRate is not match with the final totalRowCnts, it will result to compute
             * error samplerows and error statistics. So we should compute right sampleRate again.
             */
            (void)compute_sample_size(vacstmt, 0, NULL, onerel->rd_id, vacstmt->tableidx);
            numrows = ceil(vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts *
                           vacstmt->pstGlobalStatEx[vacstmt->tableidx].sampleRate);
            totalrows = vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts;

            /* Decide analyze each column with execute query for dfs/delta table.  */
            if (analyzemode == ANALYZEMAIN || analyzemode == ANALYZEDELTA) {
                set_doquery_flag(vacstmt->pstGlobalStatEx);
            }

            /*
             * We need do analyze to compute statistic for sample table only
             * required sample table on coordinator.
             *
             * We only collect extended statistics for replicate table when it is not empty
             */
            if (vacstmt->pstGlobalStatEx[vacstmt->tableidx].exec_query ||
                (replicate_needs_extstats && numrows > 0 && totalrows > 0)) {
                vacstmt->pstGlobalStatEx[vacstmt->tableidx].exec_query = true;
                do_analyze_sampletable(onerel,
                    vacstmt,
                    attr_cnt,
                    vacattrstats,
                    hasindex,
                    nindexes,
                    indexdata,
                    inh,
                    Irel,
                    totalrows,
                    numrows,
                    analyzemode,
                    pstHdfsSampleRows);
            }
        } else {
            /*
             * Supported replicate table uses query to collect statistics,
             * set 'exec_query' to mark it's analyze mode even it is empty
             */
            if (replicate_needs_extstats) {
                vacstmt->pstGlobalStatEx[vacstmt->tableidx].exec_query = true;
            }

            /* We still insert a record to pg_statistic for extended stats even the table is empty */
            for (i = 0; i < attr_cnt; ++i) {
                VacAttrStats* stats = vacattrstats[i];
                if (stats->num_attrs > 1) {
                    stats->stats_valid = true;
                    update_attstats(RelationGetRelid(onerel), STARELKIND_CLASS, inh, 1, &stats,
                                    RelationGetRelPersistence(onerel));
                }
            }
        }
    }

    if (!inh) {
        /* Update the pg_class for relation and index */
        update_pages_and_tuples_pgclass(onerel,
            vacstmt,
            attr_cnt,
            vacattrstats,
            hasindex,
            nindexes,
            indexdata,
            Irel,
            relpages,
            totalrows,
            totaldeadrows,
            numrows,
            inh);

		/*
		 * 向统计信息收集器报告 ANALYZE 信息。但是,如果正在进行继承的统计信息收集,
		 * 我们不应该报告,因为统计信息收集器仅跟踪每个表的统计信息。
		 */
        pgstat_report_analyze(onerel, totalrows, totaldeadrows);
    }

	/*
	 * 如果不是 VACUUM ANALYZE 的一部分,让索引 AMs 进行清理操作
	 */
	if (!(vacstmt->options & VACOPT_VACUUM)) {
	    cleanup_indexes(nindexes, Irel, onerel, elevel);
	}
	
	/* 索引处理完毕,关闭索引 */
	vac_close_indexes(nindexes, Irel, NoLock);
	
	/* 如果适当,记录操作日志 */
	if (IsAutoVacuumWorkerProcess() && u_sess->attr.attr_storage.Log_autovacuum_min_duration >= 0) {
	    if (u_sess->attr.attr_storage.Log_autovacuum_min_duration == 0 ||
	        TimestampDifferenceExceeds(
	            starttime, GetCurrentTimestamp(), u_sess->attr.attr_storage.Log_autovacuum_min_duration))
	        ereport(LOG,
	            (errmsg("automatic analyze of table \"%s.%s.%s\" system usage: %s",
	                get_and_check_db_name(u_sess->proc_cxt.MyDatabaseId),
	                get_namespace_name(RelationGetNamespace(onerel)),
	                RelationGetRelationName(onerel),
	                pg_rusage_show(&ru0))));
	}
	
	/* 执行分析完成后的清理操作 */
	do_analyze_finalize(caller_context, save_userid, save_sec_context, save_nestlevel, analyzemode);

    return;
}

http://www.niftyadmin.cn/n/4984314.html

相关文章

元壤教育“AIGC大模型应用开发工程师”课纲,学习这套课程就够了

元壤教育&#xff08;公众号ID&#xff1a;yuanrang_edu&#xff09;&#xff1a;专注于AIGC大模型应用开发工程师和AIGC数字人全栈运营师就业培训&#xff0c;帮助3000万大学生和职业人士构建AIGC新职场的高速公路。 人工智能是新电力。正如大约 100 年前电力改变了许多行业一…

C++ 布置new 布局new placement new

C 布置new 布局new placement new new操作符(operator new)的语法 new操作符(operator new)&#xff0c; 在头文件#include <new> 定义。 主要包括以下几类&#xff1a; 可替换的分配函数 void* operator new ( std::size_t count );&#xff0c;如T* p1 new T;可替…

QT基础教程之六布局管理器和常用控件

QT基础教程之六布局管理器和常用控件 布局管理器 所谓 GUI 界面&#xff0c;归根结底&#xff0c;就是一堆组件的叠加。我们创建一个窗口&#xff0c;把按钮放上面&#xff0c;把图标放上面&#xff0c;这样就成了一个界面。在放置时&#xff0c;组件的位置尤其重要。我们必须…

常用框架分析(7)-Flutter

框架分析&#xff08;7&#xff09;-Flutter 专栏介绍Flutter核心思想Flutter的特点快速开发跨平台高性能美观的用户界面 Flutter的架构框架层引擎层平台层 开发过程使用Dart语言编写代码编译成原生代码热重载工具和插件 优缺点优点跨平台开发高性能美观的用户界面热重载强大的…

成集云 | 抖店客户静默下单催付数据同步钉钉 | 解决方案

源系统成集云目标系统 方案介绍 随着各品牌全渠道铺货&#xff0c;主播在平台上直播时客户下了订单后不能及时付款&#xff0c;第一时间客户收不到提醒&#xff0c;不仅造成了客户付款率下降&#xff0c;更大量消耗了企业的人力成本和经济。而成集云与钉钉深度合作&#xff0…

【LeetCode】28 . 找出字符串中第一个匹配项的下标

28 . 找出字符串中第一个匹配项的下标&#xff08;简单&#xff09; 方法&#xff1a;双指针法 思路 使用 find 函数枚举原串 ss 中的每个字符作为「发起点」&#xff0c;每次从原串的「发起点」和匹配串的「首位」开始尝试匹配&#xff1a; 匹配成功&#xff1a;返回本次匹配…

如何解决Memcached缓存击穿和雪崩问题

原文 Memcached是一种快速、高性能的分布式内存对象缓存系统&#xff0c;广泛应用于Web应用的缓存中。然而&#xff0c;Memcached也存在一些常见的问题&#xff0c;如缓存击穿和缓存雪崩。本文将介绍什么是缓存击穿和缓存雪崩&#xff0c;并提供一些解决这些问题的方法&#x…

兴业数金一面凉经

1.简单介绍一下自己 2.实际java使用时间长短 3.spring boot怎么学习的 学java都学了哪些东西 4.Spring boot全局异常处理怎么做&#xff0c;有几种方式 5.鉴权和登录都怎么实现 是用gateway吗 6.前端用的什么技术栈 前端发送过来一个请求经过哪些流程 7.项目里挑选一个模…