关于vacuum的基础知识,参考,本篇从源码层继续学习
https://blog.csdn.net/Hehuyi_In/article/details/102992065
https://blog.csdn.net/Hehuyi_In/article/details/128279210
一、 准备知识
为了方便后面的学习,这里把一部分后面函数经常出现的内容提到前面。
1. VacuumStmt结构体
表示vacuum与analyze语句的结构体,在parsenodes.h文件。虽然是两个语句,但用一个结构体就可以表示。
/* ----------------------
* Vacuum and Analyze Statements
*
* Even though these are nominally two statements, it's convenient to use
* just one node type for both.
* ----------------------
*/
typedef struct VacuumStmt
{
NodeTag type;
List *options; /* list of DefElem nodes,详情参考下方 */
List *rels; /* list of VacuumRelation, or NIL for all,待操作的表,为空表示所有表,详情参考下方*/
bool is_vacuumcmd; /* true for VACUUM, false for ANALYZE,区分是vacuum还是analyze语句 */
} VacuumStmt;
2. VacuumRelation结构体
就是前面的 List *rels; 待操作的表信息,包括表名、oid、列名等
/*
* Info about a single target table of VACUUM/ANALYZE.
*
* If the OID field is set, it always identifies the table to process.
* Then the relation field can be NULL; if it isn't, it's used only to report
* failure to open/lock the relation.
*/
typedef struct VacuumRelation
{
NodeTag type;
RangeVar *relation; /* table name to process, or NULL */
Oid oid; /* table's OID; InvalidOid if not looked up */
List *va_cols; /* list of column names, or NIL for all */
} VacuumRelation;
3. VacuumParams结构体
vacuum与analyze语句的参数,其中freeze相关参数含义参考:https://blog.csdn.net/Hehuyi_In/article/details/128309607
/*
* 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
{
bits32 options; /* bitmask of VACOPT_*,下面会详细介绍 */
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,强制进行用于事务回卷的vacuum? */
int log_min_duration; /* minimum execution threshold in ms at which verbose logs are activated, -1 to use default,执行超过该时间被记录至日志,单位为ms */
VacOptValue index_cleanup; /* Do index vacuum and cleanup,进行索引vacuum和清理 */
VacOptValue truncate; /* Truncate empty pages at the end,truncate末端空页 */
/*
* The number of parallel vacuum workers. 0 by default which means choose
* based on the number of indexes. -1 indicates parallel vacuum is
* disabled. 并行vacuum workers数
*/
int nworkers;
} VacuumParams;
4. VacuumParams->options标记位
这也是最前面VacuumStmt结构体的List *options;
/* flag bits for VacuumParams->options */
#define VACOPT_VACUUM 0x01 /* do VACUUM */
#define VACOPT_ANALYZE 0x02 /* do ANALYZE */
#define VACOPT_VERBOSE 0x04 /* print progress info */
#define VACOPT_FREEZE 0x08 /* FREEZE option */
#define VACOPT_FULL 0x10 /* FULL (non-concurrent) vacuum */
#define VACOPT_SKIP_LOCKED 0x20 /* skip if cannot get lock */
#define VACOPT_PROCESS_TOAST 0x40 /* process the TOAST table, if any */
#define VACOPT_DISABLE_PAGE_SKIPPING 0x80 /* don't skip any pages */
二、 ExecVacuum()函数
vacuum相关操作在vacuum.c中,当手动执行vacuum及analyze命令,其主入口为ExecVacuum()函数——主要负责为做一系列准备工作(语句解析、选项设置与检查、参数设置等),核心是调用vacuum()函数。
主要参数
- ParseState:解析阶段生成的语句,其定义在parse_node.h
- VacuumStmt:vacuum和analyze的语句,参考前面
- isTopLevel:是否为顶层语句
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
* This is mainly a preparation wrapper for the real operations that will
* happen in vacuum().
*/
void
ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
VacuumParams params;
bool verbose = false;
bool skip_locked = false;
bool analyze = false;
bool freeze = false;
bool full = false;
bool disable_page_skipping = false;
bool process_toast = true;
ListCell *lc;
/* index_cleanup and truncate values unspecified for now */
params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
params.truncate = VACOPTVALUE_UNSPECIFIED;
/* By default parallel vacuum is enabled */
params.nworkers = 0;
/* Parse options list,解析vacuum和analyze命令参数列表 */
foreach(lc, vacstmt->options)
{
DefElem *opt = (DefElem *) lfirst(lc);
/* Parse common options for VACUUM and ANALYZE,通用参数项verbose,skip_locked */
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized ANALYZE option \"%s\"", opt->defname),
parser_errposition(pstate, opt->location)));
/* Parse options available on VACUUM
vacuum专用参数:analyze, freeze, full, disable_page_skipping, index_cleanup, process_toast,truncate,parallel */
else if (strcmp(opt->defname, "analyze") == 0)
analyze = defGetBoolean(opt);
else if (strcmp(opt->defname, "freeze") == 0)
freeze = defGetBoolean(opt);
else if (strcmp(opt->defname, "full") == 0)
full = defGetBoolean(opt);
else if (strcmp(opt->defname, "disable_page_skipping") == 0)
disable_page_skipping = defGetBoolean(opt);
else if (strcmp(opt->defname, "index_cleanup") == 0)
{
/* Interpret no string as the default, which is 'auto' */
if (!opt->arg)
params.index_cleanup = VACOPTVALUE_AUTO;
else
{
char *sval = defGetString(opt);
/* Try matching on 'auto' string, or fall back on boolean */
if (pg_strcasecmp(sval, "auto") == 0)
params.index_cleanup = VACOPTVALUE_AUTO;
else
params.index_cleanup = get_vacoptval_from_boolean(opt);
}
}
else if (strcmp(opt->defname, "process_toast") == 0)
process_toast = defGetBoolean(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacoptval_from_boolean(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("parallel option requires a value between 0 and %d",
MAX_PARALLEL_WORKER_LIMIT),
parser_errposition(pstate, opt->location)));
}
else
{
int nworkers;
nworkers = defGetInt32(opt);
if (nworkers < 0 || nworkers > MAX_PARALLEL_WORKER_LIMIT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("parallel workers for vacuum must be between 0 and %d",
MAX_PARALLEL_WORKER_LIMIT),
parser_errposition(pstate, opt->location)));
/*
* Disable parallel vacuum, if user has specified parallel
* degree as zero.
*/
if (nworkers == 0)
params.nworkers = -1;
else
params.nworkers = nworkers;
}
}
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized VACUUM option \"%s\"", opt->defname),
parser_errposition(pstate, opt->location)));
}
/* Set vacuum options,设置vacuum选项 */
params.options =
(vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE) |
(verbose ? VACOPT_VERBOSE : 0) |
(skip_locked ? VACOPT_SKIP_LOCKED : 0) |
(analyze ? VACOPT_ANALYZE : 0) |
(freeze ? VACOPT_FREEZE : 0) |
(full ? VACOPT_FULL : 0) |
(disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0) |
(process_toast ? VACOPT_PROCESS_TOAST : 0);
/* sanity checks on options,检查vacuum选项*/
Assert(params.options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((params.options & VACOPT_VACUUM) ||
!(params.options & (VACOPT_FULL | VACOPT_FREEZE)));
if ((params.options & VACOPT_FULL) && params.nworkers > 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present. 如果语句中出现了column list,需要确保声明了analyze选项
*/
if (!(params.options & VACOPT_ANALYZE))
{
ListCell *lc;
foreach(lc, vacstmt->rels)
{
VacuumRelation *vrel = lfirst_node(VacuumRelation, lc);
if (vrel->va_cols != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ANALYZE option must be specified when a column list is provided")));
}
}
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
*/
if (params.options & VACOPT_FREEZE)
{
params.freeze_min_age = 0;
params.freeze_table_age = 0;
params.multixact_freeze_min_age = 0;
params.multixact_freeze_table_age = 0;
}
else
{
params.freeze_min_age = -1;
params.freeze_table_age = -1;
params.multixact_freeze_min_age = -1;
params.multixact_freeze_table_age = -1;
}
/* user-invoked vacuum is never "for wraparound" */
params.is_wraparound = false;
/* user-invoked vacuum never uses this parameter */
params.log_min_duration = -1;
/* Now go through the common routine,核心内容,调用vacuum函数 */
vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel);
}
三、 vacuum()函数
VACUUM 和ANALYZE命令的内部入口。进行一些预检查、内存上下文分配与切换、构造待处理relation list,并确定是否要use_own_xacts,核心是调用vacuum_rel与analyze_rel函数对每个表进行处理。
主要参数:
- relations:参考前面VacuumRelation。如果非空,表示指定要vacuum什么表,否则会处理db中的所有表。如果提供了OID,将处理该oid对应的表,否则由VacuumRelation的RangeVar参数(表名)指示。
- params:参考前面
- bstrategy:buffer的访问策略,通常是NULL,但在autovacuum中会被传值,用以在多个vacuum()函数使用相同策略
- isTopLevel:由ProcessUtility函数传入
首先是一些预检查
/*
* Internal entry point for VACUUM and ANALYZE commands.
*
* relations, if not NIL, is a list of VacuumRelation to process; otherwise,
* we process all relevant tables in the database. For each VacuumRelation,
* if a valid OID is supplied, the table with that OID is what to process;
* otherwise, the VacuumRelation's RangeVar indicates what to process.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
* isTopLevel should be passed down from ProcessUtility.
*
* It is the caller's responsibility that all parameters are allocated in a
* memory context that will not disappear at transaction commit.
*/
void
vacuum(List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel)
{
static bool in_vacuum = false;
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
Assert(params != NULL);
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,但可以运行analyze
*/
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.
*/
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")));
/* sanity check for PROCESS_TOAST */
if ((params->options & VACOPT_FULL) != 0 &&
(params->options & VACOPT_PROCESS_TOAST) == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PROCESS_TOAST required with VACUUM FULL")));
/*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
if ((params->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess())
pgstat_vacuum_stat();
跨事务存储需要分配特殊的内存上下文,内存上下文切换,buffer访问策略设置
/*
* 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.
*/
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;
若relations参数不为空,构造对应列表用于处理;否则,会处理db中所有表。
/*
* Build list of relation(s) to process, putting any new data in
* vac_context for safekeeping.
*/
if (relations != NIL)
{
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);
确定是否需要开启/提交自己的事务use_own_xacts。
对于vacuum(无论是否有analyze),总是需要use_own_xacts,以便尽快释放锁。
对于analyze(非vacuum):
- autovacuum worker进程处理,需要use_own_xacts,以便尽快释放锁。
- 如果在一个事务块内部,则无法use_own_xacts。
- 如果操作多个表而又不在一个事务块中,需要use_own_xacts,以便尽快释放锁。
- 如果只操作一个表,没有必要use_own_xacts。
/*
* 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.
*/
if (params->options & VACOPT_VACUUM)
use_own_xacts = true;
else
{
Assert(params->options & VACOPT_ANALYZE);
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().
*/
if (use_own_xacts)
{
Assert(!in_outer_xact);
/* ActiveSnapshot is not set by autovacuum */
if (ActiveSnapshotSet())
PopActiveSnapshot();
/* matches the StartTransaction in PostgresMain() */
CommitTransactionCommand();
}
核心部分:vacuum_rel与analyze_rel函数负责对每个表进行处理
/* Turn vacuum cost accounting on or off, and set/clear in_vacuum */
PG_TRY();
{
ListCell *cur;
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
VacuumPageHit = 0;
VacuumPageMiss = 0;
VacuumPageDirty = 0;
VacuumCostBalanceLocal = 0;
VacuumSharedCostBalance = NULL;
VacuumActiveNWorkers = NULL;
/*
* Loop to process each selected relation. 循环处理每个表
*/
foreach(cur, relations)
{
VacuumRelation *vrel = lfirst_node(VacuumRelation, cur);
if (params->options & VACOPT_VACUUM)
{
if (!vacuum_rel(vrel->oid, vrel->relation, params))
continue;
}
if (params->options & VACOPT_ANALYZE)
{
/*
* If using separate xacts, start one for analyze. Otherwise,
* we can use the outer transaction.
*/
if (use_own_xacts)
{
StartTransactionCommand();
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
}
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();
}
}
}
}
PG_FINALLY();
{
in_vacuum = false;
VacuumCostActive = false;
}
PG_END_TRY();
…
/*
* Clean up working storage --- note we must do this after
* StartTransactionCommand, else we might be trying to delete the active
* context!
*/
MemoryContextDelete(vac_context);
vac_context = NULL;
}
由于本系列主要学习vacuum操作,analyze_rel函数的分析暂时跳过了。
三、 vacuum_rel()函数
vacuum分为两类——常规vacuum(lazy vacuum)与full vacuum,本函数的核心就是调用函数进行lazy vacuum(table_relation_vacuum函数)或者full vacuum(cluster_rel函数)。
如果是full模式,需要获取8级表锁,lazy模式则只需要4级表锁。若表打开或加锁失败,则报错退出。
/*
* vacuum_rel() -- vacuum one heap relation
*
* Returns true if it's okay to proceed with a requested ANALYZE
* operation on this table.
*
* At entry and exit, we are not inside a transaction.
*/
static bool
vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
{
LOCKMODE lmode;
Relation rel;
LockRelId lockrelid;
Oid toast_relid;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
Assert(params != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
…
/*
* Determine the type of lock we want --- hard exclusive lock for a FULL
* vacuum, but just ShareUpdateExclusiveLock for concurrent vacuum. Either
* way, we can be sure that no other backend is vacuuming the same table.
*/
lmode = (params->options & VACOPT_FULL) ?
AccessExclusiveLock : ShareUpdateExclusiveLock;
/* open the relation and get the appropriate lock on it */
rel = vacuum_open_relation(relid, relation, params->options,
params->log_min_duration >= 0, lmode);
/* leave if relation could not be opened or locked */
if (!rel)
{
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
- 检查用户权限,是否需要跳过该表vacuum
- 检查表是否可以vacuum
- 跳过其他进程的temp表
- 跳过分区表,分区表的vacuum应该针对它们的子表(已加入处理队列中)
/*
* Check if relation needs to be skipped based on ownership.
*/
if (!vacuum_is_relation_owner(RelationGetRelid(rel),
rel->rd_rel,
params->options & VACOPT_VACUUM))
{
relation_close(rel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
/*
* Check that it's of a vacuumable relkind.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_TOASTVALUE &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
ereport(WARNING,
(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
RelationGetRelationName(rel))));
relation_close(rel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
/*
* Silently ignore tables that are temp tables of other backends
*/
if (RELATION_IS_OTHER_TEMP(rel))
{
relation_close(rel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
/*
* Silently ignore partitioned tables as there is no work to be done. The
* useful work is on their child partitions, which have been queued up for
* us separately.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
relation_close(rel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
/* It's OK to proceed with ANALYZE on this table */
return true;
}
- 获取会话级锁,主要是用于vacuum toast表
- 设置index_cleanup、truncate选项
- 如果调用者要求处理toast表,非full模式下,获取其relid;full模式下,cluster_rel函数会自动重建toast表,因此不需要重复处理
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
* relation's TOAST table (if any) secure in the knowledge that no one is
* deleting the parent relation.
*
* NOTE: this cannot block, even if someone else is waiting for access,
* because the lock manager knows that both lock requests are from the
* same process.
*/
lockrelid = rel->rd_lockInfo.lockRelId;
LockRelationIdForSession(&lockrelid, lmode);
/*
* Set index_cleanup option based on index_cleanup reloption if it wasn't
* specified in VACUUM command, or when running in an autovacuum worker
*/
if (params->index_cleanup == VACOPTVALUE_UNSPECIFIED)
{
StdRdOptIndexCleanup vacuum_index_cleanup;
if (rel->rd_options == NULL)
vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
else
vacuum_index_cleanup =
((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
params->index_cleanup = VACOPTVALUE_AUTO;
else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
params->index_cleanup = VACOPTVALUE_ENABLED;
else
{
Assert(vacuum_index_cleanup ==
STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
params->index_cleanup = VACOPTVALUE_DISABLED;
}
}
/*
* Set truncate option based on truncate reloption if it wasn't specified
* in VACUUM command, or when running in an autovacuum worker
*/
if (params->truncate == VACOPTVALUE_UNSPECIFIED)
{
if (rel->rd_options == NULL ||
((StdRdOptions *) rel->rd_options)->vacuum_truncate)
params->truncate = VACOPTVALUE_ENABLED;
else
params->truncate = VACOPTVALUE_DISABLED;
}
/*
* Remember the relation's TOAST relation for later, if the caller asked
* us to process it. In VACUUM FULL, though, the toast table is
* automatically rebuilt by cluster_rel so we shouldn't recurse to it.
*/
if ((params->options & VACOPT_PROCESS_TOAST) != 0 &&
(params->options & VACOPT_FULL) == 0)
toast_relid = rel->rd_rel->reltoastrelid;
else
toast_relid = InvalidOid;
…
核心工作——进行lazy vacuum(table_relation_vacuum函数)或者full vacuum(cluster_rel函数)。如果该表有toast表,会对toast表再调用vacuum_rel函数。
/*
* Do the actual work --- either FULL or "lazy" vacuum
*/
if (params->options & VACOPT_FULL)
{
ClusterParams cluster_params = {0};
/* close relation before vacuuming, but hold lock until commit */
relation_close(rel, NoLock);
rel = NULL;
if ((params->options & VACOPT_VERBOSE) != 0)
cluster_params.options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
table_relation_vacuum(rel, params, vac_strategy);
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* all done with this class, but hold lock until commit */
if (rel)
relation_close(rel, NoLock);
/*
* Complete the transaction and free all temporary memory used.
*/
PopActiveSnapshot();
CommitTransactionCommand();
/*
* If the relation has a secondary toast rel, vacuum that too while we
* still hold the session lock on the main table. Note however that
* "analyze" will not get done on the toast table. This is good, because
* the toaster always uses hardcoded index access and statistics are
* totally unimportant for toast relations.
*/
if (toast_relid != InvalidOid)
vacuum_rel(toast_relid, NULL, params);
/*
* Now release the session-level lock on the main table.
*/
UnlockRelationIdForSession(&lockrelid, lmode);
/* Report that we really did it. */
return true;
}
后面,我们继续学习lazy vacuum(table_relation_vacuum函数)与full vacuum(cluster_rel函数)。
参考:
《PostgreSQL数据库内核分析》
PostgreSQL 源码解读(125)- MVCC#9(vacuum-主流程)_ITPUB博客
PostgreSQL 源码解读(126)- MVCC#10(vacuum过程)_ITPUB博客
http://blog.itpub.net/6906/viewspace-2564441/