列存储(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_relation 和 analyze_rel_internal 函数分别负责打开指定的关系(表),以便进行分析操作和执行实际的表分析操作,包括收集统计信息、更新系统表等。在本文中,我们重点来学习这两个函数。
analyze_get_relation 函数
analyze_get_relation 函数是用于在进行分析操作(analyze 或 vacuum)之前,打开要分析的关系(表),并获取适当的锁来确保分析的正确执行。
- 入参:
- Oid relid:表示待获取的关系的对象ID。这个参数指定了要获取哪个具体的关系。
- VacuumStmt* vacstmt:这是一个指向 VacuumStmt 结构的指针,用于表示执行 ANALYZE 或 VACUUM 命令的上下文。VacuumStmt 结构中包含了执行命令的相关信息,例如要分析的关系、分析选项等。
- 返回值:
- 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 数据库中用于表示 VACUUM 和 ANALYZE 命令的数据结构。它在数据库源代码中定义,用于存储 VACUUM 和 ANALYZE 命令的各种参数和选项。以下是 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 PGXCvoid* HDFSDnWorkFlow; /* @hdfs HDFSDnWorkFlow 存储分析操作相关信息 */
#endifbool isForeignTables; /* @hdfs 当运行 "analyze [verbose] foreign table;" 命令时为 true */bool isPgFdwForeignTables; /* 当外部表的 fdw 是 gc_fdw 时为 true */
#ifdef ENABLE_MOTbool 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 PGXCDistributionType disttype; /* 分析表的分布类型。 */
#endifAdaptMem memUsage; /* 分配给语句的自适应内存 */Oid curVerifyRel; /* 当前的关系用于数据库模式以发送远程查询 */bool isCascade; /* 用于验证表 */
} VacuumStmt;
Relation 结构体
RelationData 结构体包含了关系的各种属性,如物理标识符、缓存信息、元组描述、索引信息、触发器信息等。这个数据结构在数据库系统中用于管理关系的元数据和缓存信息,以提高查询性能和管理操作的效率。下面详细解释一下 RelationData 数据结构的作用:
- 元数据存储: RelationData 存储了关系的元数据,如物理标识符、元组描述、关系的对象ID等。这些信息对于数据库的正常操作是必需的,它们被用于查询优化、访问控制、数据完整性维护等。
- 缓存管理: 关系的部分数据被缓存在 RelationData 中,以提高查询性能。例如,索引元组、触发器信息等可能被缓存在这个数据结构中,避免了频繁的磁盘访问。
- 查询优化: 关系的统计信息、索引信息等可以帮助查询优化器选择最优的查询计划。RelationData 中的信息可以帮助数据库系统评估不同查询计划的代价,并选择最佳的执行路径。
- 元数据操作: 数据库系统需要在运行时处理与关系相关的操作,如表的创建、删除、修改等。RelationData 中的元数据信息可以帮助数据库系统执行这些操作,确保数据的一致性和完整性。
- 缓存管理与复用: 数据库系统会缓存 RelationData 数据结构,避免了频繁的元数据查询和磁盘访问。这样可以减少系统开销,提高操作的效率。
- 触发器和约束管理: RelationData 存储了关于触发器、约束和重写规则的信息,这些信息在数据修改时起着关键作用。例如,插入、更新或删除数据时,系统需要检查关联的触发器和约束,确保数据的一致性。
- 统计信息收集: 数据库系统需要收集关于关系的统计信息,以便查询优化器进行成本估算和执行计划选择。这些统计信息可以存储在 RelationData 中,供系统使用。
- 分布式数据库管理: 对于分布式数据库系统,RelationData 可以包含与分布式定位、切片映射等相关的信息。这有助于系统进行查询路由和数据分布。
Relation 结构体函数源码如下:(路径:src/include/utils/rel.h
)
typedef struct RelationData* Relation;typedef struct RelationData {RelFileNode rd_node; // 关系的物理标识符struct SMgrRelationData* rd_smgr; // 缓存的文件句柄,或者为NULLint rd_refcnt; // 引用计数BackendId rd_backend; // 拥有该临时关系的后端IDbool rd_isscannable; // 关系是否可被扫描bool rd_isnailed; // 关系是否固定在缓存中bool rd_isvalid; // 关系缓存条目是否有效char rd_indexvalid; // rd_indexlist的状态: 0 = 无效, 1 = 有效, 2 = 临时强制bool rd_islocaltemp; // 关系是否是本会话的临时关系SubTransactionId rd_createSubid; // 关系在当前事务中被创建的最高子事务IDSubTransactionId rd_newRelfilenodeSubid; // 关系文件节点变更在当前事务中生存的最高子事务IDForm_pg_class rd_rel; // RELATION元组TupleDesc rd_att; // 元组描述Oid rd_id; // 关系的对象IDLockInfoData rd_lockInfo; // 锁管理器用于锁定关系的信息RuleLock* rd_rules; // 重写规则MemoryContext rd_rulescxt; // rd_rules的私有内存上下文,如果有的话TriggerDesc* trigdesc; // 触发器信息,如果关系没有触发器则为NULLstruct RlsPoliciesDesc* rd_rlsdesc; // 行级安全策略,如果没有则为NULLList* 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.reloptionsOid rd_partHeapOid; // 分区索引的分区OIDForm_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; // 每个索引列的操作族OIDOid* rd_opcintype; // 每个操作类声明的输入数据类型的OIDRegProcedure* rd_support; // 支持函数的OIDFmgrInfo* 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; // 索引的排序规则OIDstruct FdwRoutine* rd_fdwroutine; // 缓存的函数指针,或者为NULLOid rd_toastoid; // 真实TOAST表的OID,或者为InvalidOidOid rd_bucketoid; // 在pg_hashbucket中的bucket OIDRelationBucketKey* rd_bucketkey; // 指示哪些键用于计算哈希值的bucket键信息PartitionMap* partMap; // 分区映射信息Oid parentId; // 如果由partitionGetRelation构建,这是分区OID,否则为InvalidOidstruct PgStat_TableStatus* pgstat_info; // 统计信息收集区域#ifdef PGXCRelationLocInfo* rd_locator_info; // 分布式表定位信息PartitionMap* sliceMap; // 切片映射信息
#endifRelation 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 结构,并根据需要获取锁。以下是逐行解释:
- 此行开始一个条件判断块。它首先检查
vacstmt->flags
是否表示需要进行VACUUM
操作,或者是否需要针对主分区执行VACUUM
操作。同时,它还检查vacstmt->options
是否未设置VACOPT_NOWAIT
标志,以确定是否可以进行等待式的操作。
if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&!(vacstmt->options & VACOPT_NOWAIT)) {
- 如果上述条件为真,表示满足进行
VACUUM
操作的条件,且不需要立即操作(即允许等待),那么这段代码尝试以指定的锁模式打开关系。try_relation_open
是一个函数,用于尝试打开关系并返回关系的RelationData
结构。同时,将GetLock
标志设置为true
,表示成功获取锁。
onerel = try_relation_open(relid, lockmode);
GetLock = true;
- 如果上述条件不满足,这里开始另一个条件判断块。它再次检查是否满足进行
VACUUM
操作的条件,同时尝试使用ConditionalLockRelationOid
函数以指定的锁模式对关系进行条件性加锁。
} else if ((vacuumRelation(vacstmt->flags) || vacuumMainPartition(vacstmt->flags)) &&ConditionalLockRelationOid(relid, lockmode)) {
- 如果上述条件为真,表示虽然无法立即获取锁,但满足进行
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;// 设置日志消息级别为 WARNINGmessageLevel = WARNING;// 当需要输出日志消息时,消息的严重程度将设置为 DEBUG2elevel = 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))));elseereport(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 的入参解释如下:
- Relation onerel: 要进行统计分析的目标关系(表或索引)的 Relation 结构。它包含了关于该关系的元数据和状态信息。
- VacuumStmt* vacstmt: VACUUM 或 ANALYZE 命令的信息和选项。它是对 VACUUM 或 ANALYZE 命令的解析和处理的结果,包含了执行这些操作所需的信息。
- BufferAccessStrategy bstrategy: 缓冲区访问策略对象,用于执行缓冲区管理相关操作,如预读取数据块。
- 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;
- 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 函数是分析过程中的核心部分,负责对指定表进行统计分析,获取样本数据并计算统计信息,然后更新相关的系统表信息。这个函数在分析过程中处理了许多细节,包括以下主要步骤:
- 初始化准备: 设置函数中需要用到的各种变量,如属性统计信息、索引信息、目标行数等。根据分析模式确定表的索引。
- 检查权限: 检查当前用户是否有足够的权限对表进行分析。如果没有权限,根据情况输出相应的警告信息。
- 排除特殊表: 如果正在分析的是统计信息表(pg_statistic),则输出警告并跳过分析,因为统计信息表不应该是分区表。
- 获取目标行数: 根据分析模式、表的属性信息等,计算需要分析的目标行数。
- 获取样本行数据: 根据目标行数,从表中获取样本行数据用于统计分析。如果需要在数据节点上获取样本行数据,则从数据节点获取。
- 计算统计信息: 对样本行数据进行统计分析,计算各列的统计信息,包括最小值、最大值、均值等。
- 更新统计信息: 将计算得到的统计信息更新到系统目录表 pg_statistic 中。
- 更新 pg_class 信息: 更新表的 pg_class 表中的统计信息,包括行数、死行数等。
- 报告进度: 根据需要,向统计收集器报告分析进度。
- 清理操作: 根据分析选项进行必要的清理,关闭索引等。
- 记录日志: 如果自动分析进程启动且满足日志记录条件,则记录分析操作的日志。
- 完成分析: 完成分析操作,包括回收资源和上下文。
do_analyze_rel 函数源码如下:(路径:src/gausskernel/optimizer/commands/analyze.cpp
)
入参解释:
- Relation onerel:要分析的关系(表)的 Relation 结构体,表示要对哪个关系执行分析操作。
- VacuumStmt* vacstmt:包含有关分析的信息和选项的结构体,这些信息包括要分析的对象、分析选项等。
- BlockNumber relpages:关系(表)的总块数(页数),表示该表的大小。
- bool inh:指示是否对整个继承树进行分析,即是否包括所有子表。
- int elevel:用于指定在错误报告和日志消息中使用的错误级别,表示消息的重要性。
- AnalyzeMode analyzemode:分析模式的枚举,指示分析的类型,如NORMAL、MAIN、DELTA等。
- 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; // 属性数量初始化为0int i = 0; // 循环计数器初始化为0Relation* Irel = NULL; // 索引关系(表)指针数组初始化为NULLint nindexes = 0; // 索引数量初始化为0bool hasindex = false; // 是否有索引初始化为falseVacAttrStats** vacattrstats = NULL; // 属性统计信息指针数组初始化为NULLAnlIndexData* indexdata = NULL; // 索引统计信息指针初始化为NULLint64 numrows = 0; // 估计的总行数初始化为0int64 targrows = 0; // 目标采样行数初始化为0double totalrows = 0; // 总行数初始化为0double totaldeadrows = 0; // 死亡行数初始化为0HeapTuple* rows = NULL; // 堆元组指针数组初始化为NULLPGRUsage ru0; // 进程资源使用情况结构体TimestampTz starttime = 0; // 起始时间初始化为0MemoryContext caller_context = NULL; // 调用者上下文初始化为NULLOid save_userid = 0; // 保存用户ID初始化为0int save_sec_context = 0; // 保存安全上下文初始化为0int 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))));elseereport(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);// 如果分析模式小于等于 ANALYZEMAINif (analyzemode <= ANALYZEMAIN) {// 如果 default_statistics_target 大于等于 0if (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;
}