当前位置: 首页 > news >正文

PostgreSQL数据库统计信息——analyze大致流程

standard_ProcessUtility
 | -- ExecVacuum(pstate, stmt, isTopLevel)
       | -- 参数处理
       | -- vacuum(vacstmt->rels, &params, NULL, isTopLevel)
             | -- pgstat_vacuum_stat # 向PgStat进程发送关于dead objects的信息
             | -- foreach(cur, relations)
               | -- analyze_rel(vrel->oid, vrel->relation, params, vrel->va_cols, in_outer_xact, vac_strategy)
                     | -- 确定采样函数acquirefunc
                     | -- do_analyze_rel
                           | -- pgstat_report_analyze # 向PgStat进程发送统计信息

当我们在postgresql后端执行vacuum或analyze时,执行器最后执行的代码分支最终会运行到case T_VacuumStmt,因此这也表明它们会执行相同的接口,通过参数来区分vacuum或analyze。void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)函数首先处理VACUUM和ANALYZE通用选项,取出vacstmt->options列表中的DefElem元素:如果DefElem->defname是verbose,则设置verbose布尔变量;如果DefElem->defname是skip_locked,则设置skip_locked布尔变量;如果DefElem->defname是analyze,则设置analyze布尔变量;如果DefElem->defname是freeze,则设置freeze布尔变量;如果DefElem->defname是full,则设置full变量;如果DefElem->defname是disable_page_skipping,则设置disable_page_skipping变量(根据上述选项设置VacuumParams结构体的options成员相应的掩码,如下所示);如果DefElem->defname是index_cleanup,调用get_vacopt_ternary_value函数设置VacuumParams结构体的index_cleanup;如果DefElem->defname是truncate,调用get_vacopt_ternary_value函数设置VacuumParams结构体的truncate。

/* Parameters customizing behavior of VACUUM and ANALYZE. Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set in options. */
typedef struct VacuumParams {
	int			options;		/* bitmask of VacuumOption */ //[ VACOPT_VERBOSE, VACOPT_SKIP_LOCKED, VACOPT_VACUUM, VACOPT_ANALYZE, VACOPT_FREEZE, VACOPT_FULL, VACOPT_DISABLE_PAGE_SKIPPING ]
	int			freeze_min_age; /* min freeze age, -1 to use default */
	int			freeze_table_age;	/* age at which to scan whole table */
	int			multixact_freeze_min_age;	/* min multixact freeze age, -1 to use default */
	int			multixact_freeze_table_age; /* multixact age at which to scan whole table */
	bool		is_wraparound;	/* force a for-wraparound vacuum */
	int			log_min_duration;	/* minimum execution threshold in ms at which  verbose logs are activated, -1 to use default */
	VacOptTernaryValue index_cleanup;	/* Do index vacuum and cleanup, default value depends on reloptions */
	VacOptTernaryValue truncate;	    /* Truncate empty pages at the end, default value depends on reloptions */
} VacuumParams;
/* A ternary value used by vacuum parameters. DEFAULT value is used to determine the value based on other configurations, e.g. reloptions. */
typedef enum VacOptTernaryValue{
	VACOPT_TERNARY_DEFAULT = 0,
	VACOPT_TERNARY_DISABLED,
	VACOPT_TERNARY_ENABLED,
} VacOptTernaryValue;

如果params.options设置了VACOPT_FREEZE标志,则freeze_min_age、freeze_table_age、multixact_freeze_min_age、multixact_freeze_table_age都设置为零;否则设置为-1。设置params.is_wraparound为false,意思是用户触发的vacuum永远不会是为了wraparound为目的的,设置params.log_min_duration为-1,意思是用户触发的vacuum永远不会使用该参数。

vacuum

void vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, bool isTopLevel)函数的形参relations如果没有设置为NIL,那它就是要进行vacuum的表(a list of VacuumRelation to process);否则就需要处理数据库中所有相关的表。正常情况下,bstrategy被设置为NULL,但是autovacuum流程中,该参数控制在多个vacuum函数调用之间使用相同的buffer strategy对象。isTopLevel是从ProcessUtility函数传递下来的。vacuum前期工作是:进行事务块检测、不可重入校验、为跨事务存储创建特殊内存上下文、建立要处理的relation列表和决定是否需要启动/提交自己的事务。

void vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, bool isTopLevel) {
	static bool in_vacuum = false;
    volatile bool in_outer_xact, use_own_xacts;
	const char *stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";

	/* We cannot run VACUUM inside a user transaction block; if we were inside a transaction, then our commit- and start-transaction-command calls would not have the intended effect!	There are numerous other subtle dependencies on this, too. ANALYZE (without VACUUM) can run either way. */ // 我们不能在用户事务块内运行VACUUM;如果我们在事务中,那么提交和启动事务命令调用将不会产生预期效果!还有许多其他微妙的依赖关系
	if (params->options & VACOPT_VACUUM){
		PreventInTransactionBlock(isTopLevel, stmttype);
		in_outer_xact = false;
	}else
		in_outer_xact = IsInTransactionBlock(isTopLevel);
	/* Due to static variables vac_context, anl_context and vac_strategy, vacuum() is not reentrant.  This matters when VACUUM FULL or ANALYZE calls a hostile index expression that itself calls ANALYZE. */ // 由于静态变量vac_context、anl_content和vac_strategy,vacuum()不可重入。当VACUUM FULL或ANALYZE调用本身调用ANALYZE的hostile index expression时,这很重要。
	if (in_vacuum) ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("%s cannot be executed from VACUUM or ANALYZE", stmttype)));
	/* Sanity check DISABLE_PAGE_SKIPPING option. */
	if ((params->options & VACOPT_FULL) != 0 && (params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0) ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
	/* Send info about dead objects to the statistics collector, unless we are in autovacuum --- autovacuum.c does this for itself. */ // 向统计收集器发送关于dead objects的信息,除非我们处于autovacuum。autovacuum.c自己做这件事
	if ((params->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess()) pgstat_vacuum_stat();

	/* Create special memory context for cross-transaction storage. Since it is a child of PortalContext, it will go away eventually even if we suffer an error; there's no need for special abort cleanup logic. */ // 为跨事务存储创建特殊内存上下文。由于它是PortalContext的子代,即使我们遇到错误,它最终也会消失;不需要特殊的中止清理逻辑。
	vac_context = AllocSetContextCreate(PortalContext,"Vacuum",ALLOCSET_DEFAULT_SIZES);
	/* If caller didn't give us a buffer strategy object, make one in the cross-transaction memory context. */ // 若调用者并没有给我们一个缓冲策略对象,那么在跨事务内存上下文中创建一个。
	if (bstrategy == NULL){
		MemoryContext old_context = MemoryContextSwitchTo(vac_context);
		bstrategy = GetAccessStrategy(BAS_VACUUM);
		MemoryContextSwitchTo(old_context);
	}
	vac_strategy = bstrategy;
	
	if (relations != NIL) { /* Build list of relation(s) to process, putting any new data in vac_context for safekeeping. */ // 建立要处理的关系列表,将任何新数据放在vac_context中进行安全保存
		List	   *newrels = NIL;
		ListCell   *lc;
		foreach(lc, relations) {
			VacuumRelation *vrel = lfirst_node(VacuumRelation, lc);
			List	   *sublist;
			MemoryContext old_context;
			sublist = expand_vacuum_rel(vrel, params->options);
			old_context = MemoryContextSwitchTo(vac_context);
			newrels = list_concat(newrels, sublist);
			MemoryContextSwitchTo(old_context);
		}
		relations = newrels;
	}
	else
		relations = get_all_vacuum_rels(params->options);

	/* Decide whether we need to start/commit our own transactions.
	 * For VACUUM (with or without ANALYZE): always do so, so that we can release locks as soon as possible.  (We could possibly use the outer transaction for a one-table VACUUM, but handling TOAST tables would be problematic.)
	 * For ANALYZE (no VACUUM): if inside a transaction block, we cannot start/commit our own transactions.  Also, there's no need to do so if only processing one relation.  For multiple relations when not within a transaction block, and also in an autovacuum worker, use own transactions so we can release locks sooner. */
	// 决定是否需要启动/提交自己的事务。
    // *对于VACUUM(有或没有ANALYZE):始终这样做,以便我们可以尽快释放锁。(我们可能会将外部事务用于单表VACUUM,但处理TOAST表会有问题。)
    // *对于分析(无VACUUM):如果在事务块内,我们无法启动/提交自己的事务。此外,如果只处理一个关系,则不需要这样做。对于不在事务块内的多个表,以及在autovacuum worker中,使用自己的事务,以便我们可以更快地释放锁
	if (params->options & VACOPT_VACUUM) use_own_xacts = true;
	else {
		if (IsAutoVacuumWorkerProcess()) use_own_xacts = true;
		else if (in_outer_xact) use_own_xacts = false;
		else if (list_length(relations) > 1) use_own_xacts = true;
		else use_own_xacts = false;
	}

	/* vacuum_rel expects to be entered with no transaction active; it will start and commit its own transaction.  But we are called by an SQL command, and so we are executing inside a transaction already. We commit the transaction started in PostgresMain() here, and start another one before exiting to match the commit waiting for us back in PostgresMain(). */ // vacuum_rel预计在没有活动事务的情况下进入;它将启动并提交自己的事务。但是我们被SQL命令调用,因此我们已经在事务内部执行了。我们在这里提交在PostgresMain中启动的事务,并在退出之前启动另一个事务,以匹配在PostgresMain等待我们返回的提交
	if (use_own_xacts) {		
		if (ActiveSnapshotSet()) PopActiveSnapshot(); /* ActiveSnapshot is not set by autovacuum */		
		CommitTransactionCommand(); /* matches the StartTransaction in PostgresMain() */
	}

真正处理流程如下:首先设置静态变量为in_vacuum,以防止函数重入;循环处理上一步选定的表,

		ListCell   *cur;
		in_vacuum = true; // 设置静态变量为in_vacuum,以防止函数重入
		VacuumCostActive = (VacuumCostDelay > 0);
		VacuumCostBalance,VacuumPageHit,VacuumPageMiss,VacuumPageDirty = 0;
		
		foreach(cur, relations) { /* Loop to process each selected relation. */
			VacuumRelation *vrel = lfirst_node(VacuumRelation, cur);
			if (params->options & VACOPT_VACUUM){ // VACUUM
				if (!vacuum_rel(vrel->oid, vrel->relation, params)) continue;
			}

			if (params->options & VACOPT_ANALYZE) { // ANALYZE			
				if (use_own_xacts) { /* If using separate xacts, start one for analyze. Otherwise, we can use the outer transaction. */ // 如果使用单独的XACT,请启动一个进行分析。否则,我们可以使用外部事务
					StartTransactionCommand();					
					PushActiveSnapshot(GetTransactionSnapshot()); /* functions in indexes may want a snapshot set */
				}
				analyze_rel(vrel->oid, vrel->relation, params, vrel->va_cols, in_outer_xact, vac_strategy);

				if (use_own_xacts){
					PopActiveSnapshot();
					CommitTransactionCommand();
				}else{ /* If we're not using separate xacts, better separate the ANALYZE actions with CCIs.  This avoids trouble if user says "ANALYZE t, t". */
					CommandCounterIncrement();
				}
			}
		}

analyze_rel

void analyze_rel(Oid relid, RangeVar *relation, VacuumParams *params, List *va_cols, bool in_outer_xact, BufferAccessStrategy bstrategy)函数分析单个表,形参relid标识要分析的relation oid。如果提供了形参relation,则使用其中的名称报告打开/锁定表的任何失败;一旦我们成功打开表,就不要使用它,因为它可能已过时。我们知道analyze命令可以分析整个数据库、指定的某几个表、指定表的某几个列,所以这里我们可以传入va_cols代表表的某几个列。其主要流程如下:

  1. 首先调用vacuum_open_relation函数打开表(获取ShareUpdateExclusiveLock)
  2. 调用vacuum_is_relation_owner函数校验权限,只有表的owner、数据库的owner或者超级用户才可以执行analyze;不能收集其他会话创建的临时表,直接忽略这些表;直接忽略pg_statistic系统表的收集
  3. 接着确定如何进行采样,如果是普通表或者物化视图,则采样函数采用acquire_sample_rows;如果是外部表,需要使用外部表提供的hook
  4. 向MyPgXact->vacuumFlags添加PROC_IN_ANALYZE标志,告诉其他后端,该后端进程正在进行analyze
  5. 针对非分区表onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE,直接调用do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, false, in_outer_xact, elevel)函数;针对分区表onerel->rd_rel->relhassubclass,调用do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, true, in_outer_xact, elevel)。可以看到其实就是do_analyze_rel函数的inh形参不同,对于非继承表(也就是第一种情况)inh设置为false,采样函数已经确定了并传入do_analyze_rel函数;对于继承表inh设置为true,采样函数将直接使用acquire_inherited_sample_rows函数,让该函数自主决定对每个子表选用对应的acquirefunc。
  6. 关闭表,取消MyPgXact->vacuumFlags中的PROC_IN_ANALYZE标志
void analyze_rel(Oid relid, RangeVar *relation, VacuumParams *params, List *va_cols, bool in_outer_xact, BufferAccessStrategy bstrategy) {
	Relation	onerel;
	int			elevel;
	AcquireSampleRowsFunc acquirefunc = NULL;
	BlockNumber relpages = 0;
	
	if (params->options & VACOPT_VERBOSE) elevel = INFO; /* Select logging level */
	else elevel = DEBUG2;	
	vac_strategy = bstrategy; /* Set up static variables */	
	CHECK_FOR_INTERRUPTS(); /* Check for user-requested abort. */

	/* 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. Make sure to generate only logs for ANALYZE in this case. */ // 打开关系,获取ShareUpdateExclusiveLock以确保两个ANALYZE不会同时在其上运行。(这也锁定了一个并发VACUUM,目前这并不重要,但如果我们尝试累积死元组的统计数据,可能会有影响。)如果自上次看到rel以来,rel已经被删除,我们就不需要处理它。在这种情况下,确保只生成用于分析的日志
	onerel = vacuum_open_relation(relid, relation, params->options & ~(VACOPT_VACUUM), params->log_min_duration >= 0, ShareUpdateExclusiveLock);	
	if (!onerel) return; /* leave if relation could not be opened or locked */

	/* Check if relation needs to be skipped based on ownership.  This check happens also when building the relation list to analyze for a manual operation, and needs to be done additionally here as ANALYZE could happen across multiple transactions where relation ownership could have changed in-between.  Make sure to generate only logs for ANALYZE in this case. */ // 检查是否需要根据所有权跳过关系。在构建关系列表以进行手动操作分析时,也会进行此检查,因为分析可能发生在多个事务中,其中关系所有权可能在其间发生变化,因此需要在此进行额外检查。在这种情况下,确保只生成用于分析的日志
	if (!vacuum_is_relation_owner(RelationGetRelid(onerel), onerel->rd_rel, params->options & VACOPT_ANALYZE)) {
		relation_close(onerel, ShareUpdateExclusiveLock);
		return;
	}
	/* Silently ignore tables that are temp tables of other backends --- trying to analyze these is rather pointless, since their contents are probably not up-to-date on disk.  (We don't throw a warning here; it would just lead to chatter during a database-wide ANALYZE.) */ // 默默地忽略其他后端的临时表——试图分析这些表是毫无意义的,因为它们的内容可能在磁盘上不是最新的。(我们在这里不抛出警告;它只会在数据库范围的分析过程中导致聊天。)
	if (RELATION_IS_OTHER_TEMP(onerel)){
		relation_close(onerel, ShareUpdateExclusiveLock);
		return;
	}	
	if (RelationGetRelid(onerel) == StatisticRelationId){ /* We can ANALYZE any table except pg_statistic. See update_attstats */
		relation_close(onerel, ShareUpdateExclusiveLock);
		return;
	}

	
	if (onerel->rd_rel->relkind == RELKIND_RELATION || onerel->rd_rel->relkind == RELKIND_MATVIEW) { /* Check that it's of an analyzable relkind, and set up appropriately. */		
		acquirefunc = acquire_sample_rows; /* Regular table, so we'll use the regular row acquisition function */		
		relpages = RelationGetNumberOfBlocks(onerel); /* Also get regular table's size */
	}else if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) {
		/* For a foreign table, call the FDW's hook function to see whether it supports analysis. */
		FdwRoutine *fdwroutine;
		bool		ok = false;
		fdwroutine = GetFdwRoutineForRelation(onerel, false);
		if (fdwroutine->AnalyzeForeignTable != NULL) ok = fdwroutine->AnalyzeForeignTable(onerel, &acquirefunc, &relpages);
		if (!ok){
			ereport(WARNING,(errmsg("skipping \"%s\" --- cannot analyze this foreign table",RelationGetRelationName(onerel))));
			relation_close(onerel, ShareUpdateExclusiveLock);
			return;
		}
	}else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE){
		/* For partitioned tables, we want to do the recursive ANALYZE below. */
	}else{ /* No need for a WARNING if we already complained during VACUUM */
		if (!(params->options & VACOPT_VACUUM)) ereport(WARNING,(errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",RelationGetRelationName(onerel))));
		relation_close(onerel, ShareUpdateExclusiveLock);
		return;
	}

	
	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
	MyPgXact->vacuumFlags |= PROC_IN_ANALYZE; /* OK, let's do it.  First let other backends know I'm in ANALYZE. */
	LWLockRelease(ProcArrayLock);

	/* Do the normal non-recursive ANALYZE.  We can skip this for partitioned tables, which don't contain any rows. */
	if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, false, in_outer_xact, elevel);

	/* If there are child tables, do recursive ANALYZE. */
	if (onerel->rd_rel->relhassubclass)
		do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, true, in_outer_xact, elevel);

	/* Close source relation now, but keep lock so that no one deletes it before we commit.  (If someone did, they'd fail to clean up the entries we made in pg_statistic.  Also, releasing the lock before commit would expose us to concurrent-update failures in update_attstats.) */ // 现在关闭源关系,但保持锁定,以便在提交之前没有人删除它。(如果有人这样做,他们将无法清理我们在pg_statistic中创建的条目。此外,在提交前释放锁将使我们在update_attstats中面临并发更新失败。)
	relation_close(onerel, NoLock);

	/* Reset my PGXACT flag.  Note: we need this here, and not in vacuum_rel, because the vacuum flag is cleared by the end-of-xact code. */
	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
	MyPgXact->vacuumFlags &= ~PROC_IN_ANALYZE;
	LWLockRelease(ProcArrayLock);
}

相关文章:

  • C开发环境与基础
  • Android系统_MSM8953_android10_adb连接adbd加入密码检测
  • 23设计模式之 --------- 什么是设计模式?
  • 在以「基础设施」为定位的发展阶段里,产业变成了一个可以有诸多创新的存在
  • Vue相关面试题(1)
  • 基于SpringBoot的网上购物商场管理系统
  • Python——time模块
  • 【深度学习】卷积神经网络的架构参考 以MNIST数据集为例(未调节架构)测试集正确率: 98.66%
  • C++ 哈希桶模拟实现(补充)
  • Rethinking the Inception Architecture for Computer Vision--Christian Szegedy
  • 安卓毕业设计成品基于Uniapp+SSM实现的智能课堂管理APP在线学习网
  • 基于metaRTC嵌入式webrtc的H265网页播放器实现(我与metaRTC的缘分)
  • 【设计模式】Java设计模式 - 组合模式
  • Android之Handler(上)
  • 网络协议:网络安全
  • Angular 响应式表单之下拉框
  • miaov-React 最佳入门
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • python 学习笔记 - Queue Pipes,进程间通讯
  • spark本地环境的搭建到运行第一个spark程序
  • Sublime Text 2/3 绑定Eclipse快捷键
  • Wamp集成环境 添加PHP的新版本
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 记一次用 NodeJs 实现模拟登录的思路
  • 聊聊redis的数据结构的应用
  • 浅谈Golang中select的用法
  • 区块链共识机制优缺点对比都是什么
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 用Python写一份独特的元宵节祝福
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 移动端高清、多屏适配方案
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • #大学#套接字
  • (3)STL算法之搜索
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (四)图像的%2线性拉伸
  • (一)Thymeleaf用法——Thymeleaf简介
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)创业家杂志:UCWEB天使第一步
  • (轉貼) UML中文FAQ (OO) (UML)
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • ./configure,make,make install的作用(转)
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • @SuppressWarnings注解
  • [2016.7 test.5] T1
  • [BZOJ] 2427: [HAOI2010]软件安装
  • [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated c
  • [hdu1561] The more, The Better 【树形DP】
  • [Java性能剖析]Sun JDK基本性能剖析工具介绍
  • [LeetBook]【学习日记】获取子字符串 + 颠倒子字符串顺序