postgresql源码学习(52)—— vacuum①-准备工作与主要流程

关于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, &params, NULL, isTopLevel);
}

三、 vacuum()函数

       VACUUM 和ANALYZE命令的内部入口。进行一些预检查、内存上下文分配与切换、构造待处理relation list,并确定是否要use_own_xacts,核心是调用vacuum_relanalyze_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_relanalyze_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分为两类——常规vacuumlazy vacuum)与full vacuum,本函数的核心就是调用函数进行lazy vacuumtable_relation_vacuum函数)或者full vacuumcluster_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 vacuumtable_relation_vacuum函数)或者full vacuumcluster_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/


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

相关文章

AcWing第87场周赛题解

抱歉&#xff0c;3题只有前2题&#xff0c;第三题投入产出比太低&#xff0c;就不做了 一&#xff0c;移动棋子 4797. 移动棋子 - AcWing题库 题目 难度&#xff1a;简单 思路 直接套dfs模板&#xff0c;起点通过输入时得到&#xff0c;终点&#xff08;3&#xff0c;3&am…

Kotlin中空安全操作符,异常处理和自定义异常,以及先决条件函数详解

博主前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住也分享一下给大家 &#x1f449;点击跳转到教程 一、Kotlin的可空性 null 在java中我们司空见惯的空指针异常NullPointerException,带给了我们很多麻烦。 Kotlin作为更强…

MySQL 数据库 数据完整性

什么是数据完整性&#xff1a;数据的完整性是指数据的精确性和可靠性&#xff0c;它的目的是为了防止数据库中存在不符合予以规定的数据或者因错误信息的输入而造成无效的操作。数据完整性的分类&#xff1a;实体完整性&#xff1a;每一行记录在表中是唯一的——主键约束Primar…

autojs模仿QQ长按弹窗菜单(二)

牙叔教程 简单易懂 上一节讲了列表和长按事件 autojs模仿QQ长按弹窗菜单 今天讲弹窗菜单 由粗到细, 自顶向下的写代码 我们现在要修改的文件是showMenuWindow.js function showMenuWindow(view) {let popMenuWindow ui.inflateXml(view.getContext(),<column><bu…

为什么会有右值引用?(移动构造、移动赋值)

目录 1、左值引用的缺陷 2、移动构造&#xff1a;解决临时对象的深拷贝 3、拓展&#xff1a;移动赋值 1、左值引用的缺陷 左值引用作为函数参数传递&#xff0c;减少了参数拷贝&#xff1b;但是作为函数返回值&#xff0c;并不适用于所有场景&#xff0c;比如要返回一个临…

【QT5.9】与MFC对比学习笔记-感悟篇2【2023.01.23】

是对QT的分析&#xff0c;不仅局限于QT。 二者区别 天下文章一大抄&#xff0c;技术也一样。MFC是对Windows系统API进行的封装&#xff0c;是以视类与文档类为核心的框架设计。微软20年前就已经把MVC玩的很6了&#xff0c;还有控件、动态库等等技术都是微软爸爸先搞出来的。若…

抗积分饱和PID控制算法及仿真

积分饱和现象所谓积分饱和现象是指若系统存在一个方向的偏差&#xff0c;PID 控制器的输出由于积分作用的不断累加而加大&#xff0c;从而导致执行机构达到极限位置Xmax(例如阀门开度达到最大)&#xff0c;如图所示&#xff0c;若控制器输出u(k)继续增大&#xff0c;阀门开度不…

第八层:模板

文章目录前情回顾模板模板的概念模板的特点模板分类函数模板作用语法函数模板的使用注意事项普通函数和函数模板的区别普通函数和函数模板的调用规则优先调用普通函数空模板强调函数模板函数模板可以发生重载函数模板产生更好的匹配时模板的局限性类模板作用语法类模板实例化对…