千家信息网

PostgreSQL的vacuum过程中heap_vacuum_rel函数分析

发表于:2025-01-21 作者:千家信息网编辑
千家信息网最后更新 2025年01月21日,这篇文章主要介绍"PostgreSQL的vacuum过程中heap_vacuum_rel函数分析",在日常操作中,相信很多人在PostgreSQL的vacuum过程中heap_vacuum_rel函数
千家信息网最后更新 2025年01月21日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. */voidheap_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) cContinuing.Breakpoint 1, lazy_vacuum_rel (onerel=0x7f226cd9e9a0, options=1, params=0x7ffe010d5b70, bstrategy=0x1da9708)    at vacuumlazy.c:197197     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) n215     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) n247     aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid,(gdb) p aggressive$11 = false(gdb) n249     if (options & VACOPT_DISABLE_PAGE_SKIPPING)(gdb) p onerel->rd_rel->relfrozenxid$12 = 144983(gdb) p xidFullScanLimit$13 = 4145274886(gdb)

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

(gdb) n252     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) n262     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) n289     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) n329     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) n345                          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:15601560        AtEOXact_GUC(false, save_nestlevel);(gdb)

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

0