PostgreSQL的vacuum过程中heap_vacuum_rel函数分析

这篇文章主要介绍“PostgreSQL的vacuum过程中heap_vacuum_rel函数分析”,在日常操作中,相信很多人在PostgreSQL的vacuum过程中heap_vacuum_rel函数分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”PostgreSQL的vacuum过程中heap_vacuum_rel函数分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

随县网站建设公司成都创新互联,随县网站设计制作,有大型网站制作公司丰富经验。已为随县超过千家提供企业网站建设服务。企业网站搭建\外贸网站建设要多少钱,请找那个售后服务好的随县做网站的公司定做!

本节简单介绍了PostgreSQL手工执行vacuum的实现逻辑,主要分析了ExecVacuum->vacuum->vacuum_rel->heap_vacuum_rel函数的实现逻辑。

一、数据结构

宏定义
Vacuum和Analyze命令选项

/* ----------------------
 *      Vacuum and Analyze Statements
 *      Vacuum和Analyze命令选项
 * 
 * Even though these are nominally two statements, it's convenient to use
 * just one node type for both.  Note that at least one of VACOPT_VACUUM
 * and VACOPT_ANALYZE must be set in options.
 * 虽然在这里有两种不同的语句,但只需要使用统一的Node类型即可.
 * 注意至少VACOPT_VACUUM/VACOPT_ANALYZE在选项中设置.
 * ----------------------
 */
typedef enum VacuumOption
{
    VACOPT_VACUUM = 1 << 0,     /* do VACUUM */
    VACOPT_ANALYZE = 1 << 1,    /* do ANALYZE */
    VACOPT_VERBOSE = 1 << 2,    /* print progress info */
    VACOPT_FREEZE = 1 << 3,     /* FREEZE option */
    VACOPT_FULL = 1 << 4,       /* FULL (non-concurrent) vacuum */
    VACOPT_SKIP_LOCKED = 1 << 5,    /* skip if cannot get lock */
    VACOPT_SKIPTOAST = 1 << 6,  /* don't process the TOAST table, if any */
    VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7   /* don't skip any pages */
} VacuumOption;

VacuumStmt
存储vacuum命令的option&Relation链表

typedef struct VacuumStmt
{
    NodeTag     type;//Tag
    //VacuumOption位标记
    int         options;        /* OR of VacuumOption flags */
    //VacuumRelation链表,如为NIL-->所有Relation.
    List       *rels;           /* list of VacuumRelation, or NIL for all */
} VacuumStmt;

VacuumParams
vacuum命令参数

/*
 * Parameters customizing behavior of VACUUM and ANALYZE.
 * 客户端调用VACUUM/ANALYZE时的定制化参数
 */
typedef struct VacuumParams
{
    //最小freeze age,-1表示使用默认
    int         freeze_min_age; /* min freeze age, -1 to use default */
    //扫描整个table的freeze age
    int         freeze_table_age;   /* age at which to scan whole table */
    //最小的multixact freeze age,-1表示默认
    int         multixact_freeze_min_age;   /* min multixact freeze age, -1 to
                                             * use default */
    //扫描全表的freeze age,-1表示默认
    int         multixact_freeze_table_age; /* multixact age at which to scan
                                             * whole table */
    //是否强制wraparound?
    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 */
} VacuumParams;

VacuumRelation
VACUUM/ANALYZE命令的目标表信息

/*
 * Info about a single target table of VACUUM/ANALYZE.
 * 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.
 * 如设置了OID字段,该值通常是将要处理的数据表.
 * 那么关系字段可以为空;如果不是,则仅用于报告未能打开/锁定关系。
 */
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;

BufferAccessStrategy
Buffer访问策略对象

/*
 * Buffer identifiers.
 * Buffer标识符
 * 
 * Zero is invalid, positive is the index of a shared buffer (1..NBuffers),
 * negative is the index of a local buffer (-1 .. -NLocBuffer).
 * 0表示无效,正整数表示共享buffer的索引(1..N),
 *   负数是本地buffer的索引(-1..-N)
 */
typedef int Buffer;
#define InvalidBuffer   0
/*
 * Buffer access strategy objects.
 * Buffer访问策略对象
 *
 * BufferAccessStrategyData is private to freelist.c
 * BufferAccessStrategyData对freelist.c来说是私有的
 */
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 */
    //buffers[]中的元素个数
    int         ring_size;
    /*
     * Index of the "current" slot in the ring, ie, the one most recently
     * returned by GetBufferFromRing.
     * 环形缓冲区中当前slot的索引,最近访问的通过函数GetBufferFromRing返回.
     */
    int         current;
    /*
     * True if the buffer just returned by StrategyGetBuffer had been in the
     * ring already.
     * 如正好通过StrategyGetBuffer返回的buffer已在环形缓冲区中,则返回T
     */
    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编号数组.
     * InvalidBuffer(即:0)表示我们还没有为该slot选择buffer.
     * 为了分配的简单性,这是palloc'd与结构的固定字段。
     */
    Buffer      buffers[FLEXIBLE_ARRAY_MEMBER];
}           BufferAccessStrategyData;
//Block结构体指针
typedef void *Block;
/* Possible arguments for GetAccessStrategy() */
//GetAccessStrategy()函数可取值的参数
typedef enum BufferAccessStrategyType
{
    //常规的随机访问
    BAS_NORMAL,                 /* Normal random access */
    //大规模的只读扫描
    BAS_BULKREAD,               /* Large read-only scan (hint bit updates are
                                 * ok) */
    //大量的多块写(如 COPY IN)
    BAS_BULKWRITE,              /* Large multi-block write (e.g. COPY IN) */
    //VACUUM
    BAS_VACUUM                  /* VACUUM */
} BufferAccessStrategyType;

LVRelStats

typedef struct LVRelStats
{
    /* hasindex = true means two-pass strategy; false means one-pass */
    //T表示two-pass strategy,F表示one-pass strategy
    bool        hasindex;
    /* Overall statistics about rel */
    //rel的全局统计信息
    //pg_class.relpages的上一个值
    BlockNumber old_rel_pages;  /* previous value of pg_class.relpages */
    //pages的总数
    BlockNumber rel_pages;      /* total number of pages */
    //扫描的pages
    BlockNumber scanned_pages;  /* number of pages we examined */
    //由于pin跳过的pages
    BlockNumber pinskipped_pages;   /* # of pages we skipped due to a pin */
    //跳过的frozen pages
    BlockNumber frozenskipped_pages;    /* # of frozen pages we skipped */
    //计算其元组的pages
    BlockNumber tupcount_pages; /* pages whose tuples we counted */
    //pg_class.reltuples的前值
    double      old_live_tuples;    /* previous value of pg_class.reltuples */
    //新估算的总元组数
    double      new_rel_tuples; /* new estimated total # of tuples */
    //新估算的存活元组数
    double      new_live_tuples;    /* new estimated total # of live tuples */
    //新估算的废弃元组数
    double      new_dead_tuples;    /* new estimated total # of dead tuples */
    //已清除的pages
    BlockNumber pages_removed;
    //已删除的tuples
    double      tuples_deleted;
    //实际上是非空page + 1
    BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
    /* List of TIDs of tuples we intend to delete */
    /* NB: this list is ordered by TID address */
    //将要删除的元组TIDs链表
    //注意:该链表已使用TID地址排序
    //当前的入口/条目数
    int         num_dead_tuples;    /* current # of entries */
    //数组中已分配的slots(最大已废弃元组数)
    int         max_dead_tuples;    /* # slots allocated in array */
    //ItemPointerData数组
    ItemPointer dead_tuples;    /* array of ItemPointerData */
    //扫描的索引数
    int         num_index_scans;
    //最后被清除的事务ID
    TransactionId latestRemovedXid;
    //是否存在waiter?
    bool        lock_waiter_detected;
} LVRelStats;

PGRUsage
pg_rusage_init/pg_rusage_show的状态结构体

/* State structure for pg_rusage_init/pg_rusage_show */
//pg_rusage_init/pg_rusage_show的状态结构体
typedef struct PGRUsage
{
    struct timeval tv;
    struct rusage ru;
} PGRUsage;
struct rusage
{
    struct timeval ru_utime;    /* user time used */
    struct timeval ru_stime;    /* system time used */
};
struct timeval
{
__time_t tv_sec;        /* 秒数.Seconds. */
__suseconds_t tv_usec;  /* 微秒数.Microseconds-->这个英文注释有问题. */
};

二、源码解读

heap_vacuum_rel() — 为heap relation执行VACUUM
大体逻辑如下:
1.初始化相关变量,如本地变量/日志记录级别/访问策略等
2.调用vacuum_set_xid_limits计算最老的xmin和冻结截止点
3.判断是否执行全表(不跳过pages)扫描,标记变量为aggressive
4.初始化统计信息结构体vacrelstats
5.打开索引,执行函数lazy_scan_heap进行vacuuming,关闭索引
6.更新pg_class中的统计信息
7.收尾工作

/*
 *  heap_vacuum_rel() -- perform VACUUM for one heap relation
 *  heap_vacuum_rel() -- 为heap relation执行VACUUM
 * 
 *      This routine vacuums a single heap, cleans out its indexes, and
 *      updates its relpages and reltuples statistics.
 *      该处理过程vacuum一个单独的heap,清除索引并更新relpages和reltuples统计信息.
 *
 *      At entry, we have already established a transaction and opened
 *      and locked the relation.
 *      在该调用入口,我们已经给创建了事务并且已经打开&锁定了relation.
 */
void
heap_vacuum_rel(Relation onerel, int options, VacuumParams *params,
                BufferAccessStrategy bstrategy)
{
    LVRelStats *vacrelstats;//统计信息
    Relation   *Irel;//关系指针
    int         nindexes;
    PGRUsage    ru0;//状态结构体
    TimestampTz starttime = 0;//时间戳
    long        secs;//秒数
    int         usecs;//微秒数
    double      read_rate,//读比率
                write_rate;//写比率
    //是否扫描所有未冻结的pages?
    bool        aggressive;     /* should we scan all unfrozen pages? */
    //实际上是否扫描了所有这样的pages?
    bool        scanned_all_unfrozen;   /* actually scanned all such pages? */
    TransactionId xidFullScanLimit;
    MultiXactId mxactFullScanLimit;
    BlockNumber new_rel_pages;
    BlockNumber new_rel_allvisible;
    double      new_live_tuples;
    TransactionId new_frozen_xid;
    MultiXactId new_min_multi;
    Assert(params != NULL);
    /* measure elapsed time iff autovacuum logging requires it */
    //如autovacuum日志记录需要,则测量耗费的时间
    if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
    {
        pg_rusage_init(&ru0);
        starttime = GetCurrentTimestamp();
    }
    if (options & VACOPT_VERBOSE)
        //需要VERBOSE
        elevel = INFO;
    else
        elevel = DEBUG2;
    pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
                                  RelationGetRelid(onerel));
    vac_strategy = bstrategy;
    //计算最老的xmin和冻结截止点
    //输出:OldestXmin/FreezeLimit/FreezeLimit/MultiXactCutoff/mxactFullScanLimit
    vacuum_set_xid_limits(onerel,
                          params->freeze_min_age,
                          params->freeze_table_age,
                          params->multixact_freeze_min_age,
                          params->multixact_freeze_table_age,
                          &OldestXmin, &FreezeLimit, &xidFullScanLimit,
                          &MultiXactCutoff, &mxactFullScanLimit);
    /*
     * We request an aggressive scan if the table's frozen Xid is now older
     * than or equal to the requested Xid full-table scan limit; or if the
     * table's minimum MultiXactId is older than or equal to the requested
     * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
     * 如果表的frozen Xid现在大于或等于请求的Xid全表扫描限制,则请求进行主动扫描;
     * 或者如果表的最小MultiXactId大于或等于请求的mxid全表扫描限制;
     * 或者,如果指定了disable_page_skip。
     */
    //比较onerel->rd_rel->relfrozenxid & xidFullScanLimit
    //如小于等于,则aggressive为T,否则为F
    aggressive = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
                                               xidFullScanLimit);
    //比较onerel->rd_rel->relminmxid &mxactFullScanLimit
    //如小于等于,则aggressive为T
    //否则aggressive原值为T,则为T,否则为F
    aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid,
                                              mxactFullScanLimit);
    if (options & VACOPT_DISABLE_PAGE_SKIPPING)
        //禁用跳过页,则强制为T
        aggressive = true;
    //分配统计结构体内存
    vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
    //记录统计信息
    vacrelstats->old_rel_pages = onerel->rd_rel->relpages;
    vacrelstats->old_live_tuples = onerel->rd_rel->reltuples;
    vacrelstats->num_index_scans = 0;
    vacrelstats->pages_removed = 0;
    vacrelstats->lock_waiter_detected = false;
    /* Open all indexes of the relation */
    //打开该relation所有的索引
    vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel);
    vacrelstats->hasindex = (nindexes > 0);
    /* Do the vacuuming */
    //执行vacuuming
    lazy_scan_heap(onerel, options, vacrelstats, Irel, nindexes, aggressive);
    /* Done with indexes */
    //已完成index的处理
    vac_close_indexes(nindexes, Irel, NoLock);
    /*
     * Compute whether we actually scanned the all unfrozen pages. If we did,
     * we can adjust relfrozenxid and relminmxid.
     * 计算我们实际上是否扫描了所有unfrozen pages.
     * 如果扫描了,则需要调整relfrozenxid和relminmxid.
     *
     * NB: We need to check this before truncating the relation, because that
     * will change ->rel_pages.
     * 注意:我们需要在截断relation前执行检查,因为这会改变rel_pages.
     */
    if ((vacrelstats->scanned_pages + vacrelstats->frozenskipped_pages)
        < vacrelstats->rel_pages)
    {
        Assert(!aggressive);
        scanned_all_unfrozen = false;
    }
    else
        //扫描pages + 冻结跳过的pages >= 总pages,则为T
        scanned_all_unfrozen = true;
    /*
     * Optionally truncate the relation.
     * 可选的,截断relation
     */
    if (should_attempt_truncation(vacrelstats))
        lazy_truncate_heap(onerel, vacrelstats);
    /* Report that we are now doing final cleanup */
    //通知其他进程,正在进行最后的清理
    pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
                                 PROGRESS_VACUUM_PHASE_FINAL_CLEANUP);
    /*
     * Update statistics in pg_class.
     * 更新pg_class中的统计信息.
     *
     * A corner case here is that if we scanned no pages at all because every
     * page is all-visible, we should not update relpages/reltuples, because
     * we have no new information to contribute.  In particular this keeps us
     * from replacing relpages=reltuples=0 (which means "unknown tuple
     * density") with nonzero relpages and reltuples=0 (which means "zero
     * tuple density") unless there's some actual evidence for the latter.
     * 这里的一个极端情况是,如果每个页面都是可见的,这时候根本没有扫描任何页面,
     *   那么就不应该更新relpages/reltuples,因为我们没有新信息可以更新。
     * 特别地,这阻止我们将relpages=reltuple =0(这意味着“未知的元组密度”)替换
     *   为非零relpages和reltuple=0(这意味着“零元组密度”),
     *   除非有关于后者的一些实际的证据。
     *
     * It's important that we use tupcount_pages and not scanned_pages for the
     * check described above; scanned_pages counts pages where we could not
     * get cleanup lock, and which were processed only for frozenxid purposes.
     * 对于上面描述的检查,使用tupcount_pages而不是scanned_pages是很重要的;
     * scanned_pages对无法获得清理锁的页面进行计数,这些页面仅用于frozenxid目的。
     *
     * We do update relallvisible even in the corner case, since if the table
     * is all-visible we'd definitely like to know that.  But clamp the value
     * to be not more than what we're setting relpages to.
     * 即使在极端情况下,我们也会更新relallvisible,因为如果表是all-visible的,那我们肯定想知道这个.
     * 但是不要超过我们设置relpages的值。
     *
     * Also, don't change relfrozenxid/relminmxid if we skipped any pages,
     * since then we don't know for certain that all tuples have a newer xmin.
     * 同时,如果我们跳过了所有的页面,不能更新relfrozenxid/relminmxid,
     *   因为从那时起,我们不能确定所有元组是否都有更新的xmin.
     */
    new_rel_pages = vacrelstats->rel_pages;
    new_live_tuples = vacrelstats->new_live_tuples;
    if (vacrelstats->tupcount_pages == 0 && new_rel_pages > 0)
    {
        //实际处理的tuple为0而且总页面不为0,则调整回原页数
        new_rel_pages = vacrelstats->old_rel_pages;
        new_live_tuples = vacrelstats->old_live_tuples;
    }
    visibilitymap_count(onerel, &new_rel_allvisible, NULL);
    if (new_rel_allvisible > new_rel_pages)
        new_rel_allvisible = new_rel_pages;
    new_frozen_xid = scanned_all_unfrozen ? FreezeLimit : InvalidTransactionId;
    new_min_multi = scanned_all_unfrozen ? MultiXactCutoff : InvalidMultiXactId;
    //更新pg_class中的统计信息
    vac_update_relstats(onerel,
                        new_rel_pages,
                        new_live_tuples,
                        new_rel_allvisible,
                        vacrelstats->hasindex,
                        new_frozen_xid,
                        new_min_multi,
                        false);
    /* report results to the stats collector, too */
    //同时,发送结果给统计收集器
    pgstat_report_vacuum(RelationGetRelid(onerel),
                         onerel->rd_rel->relisshared,
                         new_live_tuples,
                         vacrelstats->new_dead_tuples);
    pgstat_progress_end_command();
    /* and log the action if appropriate */
    //并在适当的情况下记录操作
    if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
    {
        //autovacuum && 参数log_min_duration >= 0
        TimestampTz endtime = GetCurrentTimestamp();
        if (params->log_min_duration == 0 ||
            TimestampDifferenceExceeds(starttime, endtime,
                                       params->log_min_duration))
        {
            StringInfoData buf;
            char       *msgfmt;
            TimestampDifference(starttime, endtime, &secs, &usecs);
            read_rate = 0;
            write_rate = 0;
            if ((secs > 0) || (usecs > 0))
            {
                read_rate = (double) BLCKSZ * VacuumPageMiss / (1024 * 1024) /
                    (secs + usecs / 1000000.0);
                write_rate = (double) BLCKSZ * VacuumPageDirty / (1024 * 1024) /
                    (secs + usecs / 1000000.0);
            }
            /*
             * This is pretty messy, but we split it up so that we can skip
             * emitting individual parts of the message when not applicable.
             */
            initStringInfo(&buf);
            if (params->is_wraparound)
            {
                if (aggressive)
                    msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
                else
                    msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
            }
            else
            {
                if (aggressive)
                    msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n");
                else
                    msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n");
            }
            appendStringInfo(&buf, msgfmt,
                             get_database_name(MyDatabaseId),
                             get_namespace_name(RelationGetNamespace(onerel)),
                             RelationGetRelationName(onerel),
                             vacrelstats->num_index_scans);
            appendStringInfo(&buf, _("pages: %u removed, %u remain, %u skipped due to pins, %u skipped frozen\n"),
                             vacrelstats->pages_removed,
                             vacrelstats->rel_pages,
                             vacrelstats->pinskipped_pages,
                             vacrelstats->frozenskipped_pages);
            appendStringInfo(&buf,
                             _("tuples: %.0f removed, %.0f remain, %.0f are dead but not yet removable, oldest xmin: %u\n"),
                             vacrelstats->tuples_deleted,
                             vacrelstats->new_rel_tuples,
                             vacrelstats->new_dead_tuples,
                             OldestXmin);
            appendStringInfo(&buf,
                             _("buffer usage: %d hits, %d misses, %d dirtied\n"),
                             VacuumPageHit,
                             VacuumPageMiss,
                             VacuumPageDirty);
            appendStringInfo(&buf, _("avg read rate: %.3f MB/s, avg write rate: %.3f MB/s\n"),
                             read_rate, write_rate);
            appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
            ereport(LOG,
                    (errmsg_internal("%s", buf.data)));
            pfree(buf.data);
        }
    }
}

三、跟踪分析

测试脚本

11:45:37 (xdb@[local]:5432)testdb=# vacuum t1;

启动gdb,设置断点
注:PG主线函数名称已改为heap_vacuum_rel,PG 11.1仍为lazy_vacuum_rel

(gdb) c
Continuing.
Breakpoint 1, lazy_vacuum_rel (onerel=0x7f226cd9e9a0, options=1, params=0x7ffe010d5b70, bstrategy=0x1da9708)
    at vacuumlazy.c:197
197     TimestampTz starttime = 0;
(gdb)

输入参数
relation

(gdb) p *onerel
$1 = {rd_node = {spcNode = 1663, dbNode = 16402, relNode = 50820}, rd_smgr = 0x0, rd_refcnt = 1, rd_backend = -1, 
  rd_islocaltemp = false, rd_isnailed = false, rd_isvalid = true, rd_indexvalid = 0 '\000', rd_statvalid = false, 
  rd_createSubid = 0, rd_newRelfilenodeSubid = 0, rd_rel = 0x7f226cd9ebb8, rd_att = 0x7f226cd9ecd0, rd_id = 50820, 
  rd_lockInfo = {lockRelId = {relId = 50820, dbId = 16402}}, rd_rules = 0x0, rd_rulescxt = 0x0, trigdesc = 0x0, 
  rd_rsdesc = 0x0, rd_fkeylist = 0x0, rd_fkeyvalid = false, rd_partkeycxt = 0x0, rd_partkey = 0x0, rd_pdcxt = 0x0, 
  rd_partdesc = 0x0, rd_partcheck = 0x0, rd_indexlist = 0x0, rd_oidindex = 0, rd_pkindex = 0, rd_replidindex = 0, 
  rd_statlist = 0x0, rd_indexattr = 0x0, rd_projindexattr = 0x0, rd_keyattr = 0x0, rd_pkattr = 0x0, rd_idattr = 0x0, 
  rd_projidx = 0x0, rd_pubactions = 0x0, rd_options = 0x0, rd_index = 0x0, rd_indextuple = 0x0, rd_amhandler = 0, 
  rd_indexcxt = 0x0, rd_amroutine = 0x0, rd_opfamily = 0x0, rd_opcintype = 0x0, rd_support = 0x0, rd_supportinfo = 0x0, 
  rd_indoption = 0x0, rd_indexprs = 0x0, rd_indpred = 0x0, rd_exclops = 0x0, rd_exclprocs = 0x0, rd_exclstrats = 0x0, 
  rd_amcache = 0x0, rd_indcollation = 0x0, rd_fdwroutine = 0x0, rd_toastoid = 0, pgstat_info = 0x1d5a030}
(gdb)

vacuum参数

(gdb) p *params
$2 = {freeze_min_age = -1, freeze_table_age = -1, multixact_freeze_min_age = -1, multixact_freeze_table_age = -1, 
  is_wraparound = false, log_min_duration = -1}
(gdb)

buffer访问策略对象

(gdb) p *bstrategy
$3 = {btype = BAS_VACUUM, ring_size = 32, current = 0, current_was_in_ring = false, buffers = 0x1da9718}
(gdb) 
(gdb) p *bstrategy->buffers
$4 = 0
(gdb)

1.初始化相关变量,如本地变量/日志记录级别/访问策略等

$4 = 0
(gdb) n
215     if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
(gdb) 
221     if (options & VACOPT_VERBOSE)
(gdb) 
224         elevel = DEBUG2;
(gdb) 
226     pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
(gdb) 
229     vac_strategy = bstrategy;
(gdb)

2.调用vacuum_set_xid_limits计算最老的xmin和冻结截止点
返回值均为默认值,其中OldestXmin是当前最小的活动事务ID

231     vacuum_set_xid_limits(onerel,
(gdb) 
245     aggressive = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
(gdb) p OldestXmin
$5 = 307590
(gdb) p FreezeLimit
$6 = 4245274886
(gdb) p xidFullScanLimit
$10 = 4145274886
(gdb) p MultiXactCutoff
$8 = 4289967297
(gdb) p mxactFullScanLimit
$9 = 4144967297
(gdb)

3.判断是否执行全表(不跳过pages)扫描,标记变量为aggressive,值为F

(gdb) n
247     aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid,
(gdb) p aggressive
$11 = false
(gdb) n
249     if (options & VACOPT_DISABLE_PAGE_SKIPPING)
(gdb) p onerel->rd_rel->relfrozenxid
$12 = 144983
(gdb) p xidFullScanLimit
$13 = 4145274886
(gdb)

4.初始化统计信息结构体vacrelstats

(gdb) n
252     vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
(gdb) 
254     vacrelstats->old_rel_pages = onerel->rd_rel->relpages;
(gdb) 
255     vacrelstats->old_live_tuples = onerel->rd_rel->reltuples;
(gdb) 
256     vacrelstats->num_index_scans = 0;
(gdb) 
257     vacrelstats->pages_removed = 0;
(gdb) 
258     vacrelstats->lock_waiter_detected = false;
(gdb) 
261     vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel);
(gdb) p *vacrelstats
$14 = {hasindex = false, old_rel_pages = 75, rel_pages = 0, scanned_pages = 0, pinskipped_pages = 0, 
  frozenskipped_pages = 0, tupcount_pages = 0, old_live_tuples = 10000, new_rel_tuples = 0, new_live_tuples = 0, 
  new_dead_tuples = 0, pages_removed = 0, tuples_deleted = 0, nonempty_pages = 0, num_dead_tuples = 0, max_dead_tuples = 0, 
  dead_tuples = 0x0, num_index_scans = 0, latestRemovedXid = 0, lock_waiter_detected = false}
(gdb)

5.打开索引,执行函数lazy_scan_heap进行vacuuming,关闭索引

(gdb) n
262     vacrelstats->hasindex = (nindexes > 0);
(gdb) 
265     lazy_scan_heap(onerel, options, vacrelstats, Irel, nindexes, aggressive);
(gdb) 
268     vac_close_indexes(nindexes, Irel, NoLock);
(gdb) 
(gdb) 
277     if ((vacrelstats->scanned_pages + vacrelstats->frozenskipped_pages)
(gdb) 
278         < vacrelstats->rel_pages)
(gdb) 
277     if ((vacrelstats->scanned_pages + vacrelstats->frozenskipped_pages)
(gdb) 
284         scanned_all_unfrozen = true;
(gdb) p *vacrelstats
$15 = {hasindex = true, old_rel_pages = 75, rel_pages = 75, scanned_pages = 75, pinskipped_pages = 0, 
  frozenskipped_pages = 0, tupcount_pages = 75, old_live_tuples = 10000, new_rel_tuples = 10154, new_live_tuples = 10000, 
  new_dead_tuples = 154, pages_removed = 0, tuples_deleted = 0, nonempty_pages = 75, num_dead_tuples = 0, 
  max_dead_tuples = 21825, dead_tuples = 0x1db5030, num_index_scans = 0, latestRemovedXid = 0, lock_waiter_detected = false}
(gdb) p vacrelstats->scanned_pages
$16 = 75
(gdb) p vacrelstats->frozenskipped_pages
$17 = 0
(gdb) p vacrelstats->rel_pages
$18 = 75
(gdb)

6.更新pg_class中的统计信息

(gdb) n
289     if (should_attempt_truncation(vacrelstats))
(gdb) 
293     pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
(gdb) 
317     new_rel_pages = vacrelstats->rel_pages;
(gdb) 
318     new_live_tuples = vacrelstats->new_live_tuples;
(gdb) 
319     if (vacrelstats->tupcount_pages == 0 && new_rel_pages > 0)
(gdb) 
325     visibilitymap_count(onerel, &new_rel_allvisible, NULL);
(gdb) 
326     if (new_rel_allvisible > new_rel_pages)
(gdb) p new_rel_allvisible
$19 = 0
(gdb) p new_rel_pages
$20 = 75
(gdb) n
329     new_frozen_xid = scanned_all_unfrozen ? FreezeLimit : InvalidTransactionId;
(gdb) 
330     new_min_multi = scanned_all_unfrozen ? MultiXactCutoff : InvalidMultiXactId;
(gdb) 
336                         vacrelstats->hasindex,
(gdb) 
332     vac_update_relstats(onerel,
(gdb) p new_frozen_xid
$21 = 4245274886
(gdb) p new_min_multi
$22 = 4289967297
(gdb)

7.收尾工作

(gdb) n
345                          vacrelstats->new_dead_tuples);
(gdb) 
342     pgstat_report_vacuum(RelationGetRelid(onerel),
(gdb) 
343                          onerel->rd_rel->relisshared,
(gdb) 
342     pgstat_report_vacuum(RelationGetRelid(onerel),
(gdb) 
346     pgstat_progress_end_command();
(gdb) 
349     if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
(gdb)

完成调用

411 }
(gdb) 
vacuum_rel (relid=50820, relation=0x1cdb8d0, options=1, params=0x7ffe010d5b70) at vacuum.c:1560
1560        AtEOXact_GUC(false, save_nestlevel);
(gdb)

到此,关于“PostgreSQL的vacuum过程中heap_vacuum_rel函数分析”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


标题名称:PostgreSQL的vacuum过程中heap_vacuum_rel函数分析
分享链接:http://hbruida.cn/article/ghgiig.html