声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 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 或 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 结构体是在 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 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;
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; // 缓存的文件句柄,或者为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 结构,并根据需要获取锁。以下是逐行解释:
- 此行开始一个条件判断块。它首先检查
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 函数的作用是尝试以指定的锁模式打开一个数据库关系(表、索引等)。与普通的 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 函数是用于在非阻塞的情况下尝试获取指定关系的锁,并在成功获取锁时返回 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 函数用于分析一个关系(表),它接收一个待分析的关系、分析或清理命令的语句信息、缓冲区访问策略、分析类型以及用于 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 的入参解释如下:
- 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 结构体用于管理一个循环的共享缓冲区,以便进行重用。这个结构体中存储了私有(非共享)状态,用于实现缓冲区访问策略。函数源码如下:(路径: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 结构体用于存储全局统计信息中的 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 函数的作用是对给定的表进行分析(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; // 属性数量初始化为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;
}