千家信息网

PostgreSQL的vacuum过程中lazy_vacuum_heap函数有什么作用

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,这篇文章主要介绍"PostgreSQL的vacuum过程中lazy_vacuum_heap函数有什么作用",在日常操作中,相信很多人在PostgreSQL的vacuum过程中lazy_vacuum_h
千家信息网最后更新 2026年11月04日PostgreSQL的vacuum过程中lazy_vacuum_heap函数有什么作用

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

本节简单介绍了PostgreSQL手工执行vacuum的处理流程,主要分析了ExecVacuum->vacuum->vacuum_rel->heap_vacuum_rel->lazy_scan_heap->lazy_vacuum_heap函数的实现逻辑,该函数访问堆表,标记废弃元组为未使用并在这些元组所在页面上压缩空闲空间。

一、数据结构

宏定义
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;

itemIdSort
PageRepairFragmentation/PageIndexMultiDelete的排序支持

/* * sorting support for PageRepairFragmentation and PageIndexMultiDelete * PageRepairFragmentation/PageIndexMultiDelete的排序支持 */typedef struct itemIdSortData{    //行指针数组索引    uint16      offsetindex;    /* linp array index */    //item数据页内偏移    int16       itemoff;        /* page offset of item data */    //对齐长度    uint16      alignedlen;     /* MAXALIGN(item data len) */} itemIdSortData;//结构体指针typedef itemIdSortData *itemIdSort;

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 */    //ItemPointer数组    ItemPointer dead_tuples;    /* array of ItemPointerData */    //扫描的索引数    int         num_index_scans;    //最后被清除的事务ID    TransactionId latestRemovedXid;    //是否存在waiter?    bool        lock_waiter_detected;} LVRelStats;

ItemPointer
行指针

typedef struct ItemPointerData{    BlockIdData ip_blkid;//块号    OffsetNumber ip_posid;//块内偏移}typedef ItemPointerData *ItemPointer;

二、源码解读

lazy_vacuum_heap
lazy_vacuum_heap标记废弃元组为未使用并在这些元组所在页面上压缩空闲空间,在此期间,不会访问lazy_scan_heap标记为存活元组的页面.
主要处理流程如下:
1.初始化变量
2.遍历vacrelstats->num_dead_tuples行指针数组(ItemPointer)
2.1获取块号/读取块到缓冲区中
2.2加锁,如不成功,则处理下一个元组
2.3调用lazy_vacuum_page释放空间,整理碎片
2.4获取page,获取该page的空闲空间
2.5释放缓冲,记录空闲空间
3.收尾工作

/* *  lazy_vacuum_heap() -- second pass over the heap *  lazy_vacuum_heap() -- 二次访问堆表 * *      This routine marks dead tuples as unused and compacts out free *      space on their pages.  Pages not having dead tuples recorded from *      lazy_scan_heap are not visited at all. *      lazy_vacuum_heap标记废弃元组为未使用并在这些元组所在页面上压缩空闲空间. *      在此期间,不会访问lazy_scan_heap标记没有废弃元组的页面. * * Note: the reason for doing this as a second pass is we cannot remove * the tuples until we've removed their index entries, and we want to * process index entry removal in batches as large as possible. * 注意:二次访问堆表的原因是在清除索引条目前不能清除元组, *      而且我们希望以批量的方式处理索引条目,越大越好. */static voidlazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats){    int         tupindex;//元组索引    int         npages;//页面数    PGRUsage    ru0;    Buffer      vmbuffer = InvalidBuffer;//vm缓冲    pg_rusage_init(&ru0);//初始化    npages = 0;    tupindex = 0;    //遍历废弃元组    //vacrelstats->dead_tuples数组中的元素类型ItemPointer    while (tupindex < vacrelstats->num_dead_tuples)    {        BlockNumber tblk;//块号        Buffer      buf;//缓冲        Page        page;//页面        Size        freespace;        vacuum_delay_point();        //获取块号        tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);        //以扩展方式读取buffer        buf = ReadBufferExtended(onerel, MAIN_FORKNUM, tblk, RBM_NORMAL,                                 vac_strategy);        //获取锁(不等待)        if (!ConditionalLockBufferForCleanup(buf))        {            //获取不了,释放资源,跳转到下一个元组            ReleaseBuffer(buf);            ++tupindex;            continue;        }        //释放page中的废弃元组,并整理碎片        tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats,                                    &vmbuffer);        /* Now that we've compacted the page, record its available space */        //现在已经压缩了页面(释放了空间),记录可用空间        page = BufferGetPage(buf);        freespace = PageGetHeapFreeSpace(page);        UnlockReleaseBuffer(buf);        RecordPageWithFreeSpace(onerel, tblk, freespace);        npages++;    }    if (BufferIsValid(vmbuffer))    {        //释放缓冲区        ReleaseBuffer(vmbuffer);        vmbuffer = InvalidBuffer;    }    ereport(elevel,            (errmsg("\"%s\": removed %d row versions in %d pages",                    RelationGetRelationName(onerel),                    tupindex, npages),             errdetail_internal("%s", pg_rusage_show(&ru0))));}

lazy_vacuum_page
lazy_vacuum_page释放page中的废弃元组,并整理碎片
主要处理逻辑如下:
1.初始化相关变量
2.遍历废弃元组数组
2.1获取块号,如块号不一致,跳出循环
2.2获取偏移/行指针
2.3标记为未使用,记录偏移
3.调用PageRepairFragmentation整理碎片
3.1判断和检查(严谨的编码!!!)
3.2获取偏移,初始化变量
3.3遍历行指针数组
3.3.1获取行指针lp
3.3.2如ItemId正在使用,记录到itemidbase数组中;否则标记ItemId未被使用
3.4计算数组中存储的元素个数
A.如个数为0,重置page
B.否则调用compactify_tuples压缩页
3.5为PageAddItem方法设置标记位
4.标记buffer为dirty
5.写入WAL Record
6.如all-visible,则设置页面all-visible标记
7.如page为all-visible,设置vm
8.返回下一个page的起始数组编号

/* *  lazy_vacuum_page() -- free dead tuples on a page *                   and repair its fragmentation. *  lazy_vacuum_page() -- 释放page中的废弃元组,并整理碎片 * * Caller must hold pin and buffer cleanup lock on the buffer. * 调用者必须持有buffer的pin和cleanup锁才能执行 * * tupindex is the index in vacrelstats->dead_tuples of the first dead * tuple for this page.  We assume the rest follow sequentially. * The return value is the first tupindex after the tuples of this page. * tupindex是该page中第一个废弃元组在vacrelstats->dead_tuples中的编号,我们假定余下元组是顺序的. * 返回值是该page中的元组后的第一个编号tupindex. */static intlazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,                 int tupindex, LVRelStats *vacrelstats, Buffer *vmbuffer){    //获取page    Page        page = BufferGetPage(buffer);    OffsetNumber unused[MaxOffsetNumber];//偏移数组    int         uncnt = 0;    TransactionId visibility_cutoff_xid;//事务ID    bool        all_frozen;//释放全部冻结    pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno);    //进入关键处理部分    START_CRIT_SECTION();    //遍历废弃元组数组    for (; tupindex < vacrelstats->num_dead_tuples; tupindex++)    {        BlockNumber tblk;//块号        OffsetNumber toff;//偏移        ItemId      itemid;//行指针        //根据行指针获取块号        tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);        if (tblk != blkno)            //不是同一个块,跳出循环            break;              /* past end of tuples for this block */        //获取偏移        toff = ItemPointerGetOffsetNumber(&vacrelstats->dead_tuples[tupindex]);        //获取行指针        itemid = PageGetItemId(page, toff);        //标记为未使用        ItemIdSetUnused(itemid);        //记录偏移        unused[uncnt++] = toff;    }    //整理碎片    PageRepairFragmentation(page);    /*     * Mark buffer dirty before we write WAL.     * 标记buffer为dirty     */    MarkBufferDirty(buffer);    /* XLOG stuff */    if (RelationNeedsWAL(onerel))    {        //记录WAL Record        XLogRecPtr  recptr;        recptr = log_heap_clean(onerel, buffer,                                NULL, 0, NULL, 0,                                unused, uncnt,                                vacrelstats->latestRemovedXid);        PageSetLSN(page, recptr);    }    /*     * End critical section, so we safely can do visibility tests (which     * possibly need to perform IO and allocate memory!). If we crash now the     * page (including the corresponding vm bit) might not be marked all     * visible, but that's fine. A later vacuum will fix that.     * 结束关键区域,这样我们可以安全的执行可见性检查     * (这可能需要执行IO/分配内存)     * 如果进程崩溃,页面(包括相应的vm位)可能标记为all-visible,但这也没有问题,后续vacuum会修复.     */    END_CRIT_SECTION();    /*     * Now that we have removed the dead tuples from the page, once again     * check if the page has become all-visible.  The page is already marked     * dirty, exclusively locked, and, if needed, a full page image has been     * emitted in the log_heap_clean() above.     * 现在,我们已经从页面中删除了废弃的元组,再次检查页面是否已经全部可见。     * 页面已经被标记为dirty、独占锁定,如需要,还会在log_heap_clean()中记录完整的页面镜像。     */    if (heap_page_is_all_visible(onerel, buffer, &visibility_cutoff_xid,                                 &all_frozen))        PageSetAllVisible(page);    /*     * All the changes to the heap page have been done. If the all-visible     * flag is now set, also set the VM all-visible bit (and, if possible, the     * all-frozen bit) unless this has already been done previously.     * 堆页面的所有修改已完成.如果设置了all-visible标记,同时设置VM all-visible位     * (而且,如可能,设置all-frozen位),除非先前已完成.     */    if (PageIsAllVisible(page))    {        uint8       vm_status = visibilitymap_get_status(onerel, blkno, vmbuffer);        uint8       flags = 0;        /* Set the VM all-frozen bit to flag, if needed */        //如需要,设置VM all-frozen标记位        if ((vm_status & VISIBILITYMAP_ALL_VISIBLE) == 0)            flags |= VISIBILITYMAP_ALL_VISIBLE;        if ((vm_status & VISIBILITYMAP_ALL_FROZEN) == 0 && all_frozen)            flags |= VISIBILITYMAP_ALL_FROZEN;        Assert(BufferIsValid(*vmbuffer));        if (flags != 0)            visibilitymap_set(onerel, blkno, buffer, InvalidXLogRecPtr,                              *vmbuffer, visibility_cutoff_xid, flags);    }    return tupindex;}/* * PageRepairFragmentation * * Frees fragmented space on a page. * 释放页面上的碎片空间. * * It doesn't remove unused line pointers! Please don't change this. * 该方法不会清楚未使用的行指针!因此,不要修改它. * * This routine is usable for heap pages only, but see PageIndexMultiDelete. * 该方法只用于堆页面,但注意参考PageIndexMultiDelete. * * As a side effect, the page's PD_HAS_FREE_LINES hint bit is updated. * 该方法在处理的时候,页面的PD_HAS_FREE_LINES标记位会被更新. *  */voidPageRepairFragmentation(Page page){    Offset      pd_lower = ((PageHeader) page)->pd_lower;    Offset      pd_upper = ((PageHeader) page)->pd_upper;    Offset      pd_special = ((PageHeader) page)->pd_special;    itemIdSortData itemidbase[MaxHeapTuplesPerPage];//存储数据    itemIdSort  itemidptr;    ItemId      lp;    int         nline,                nstorage,                nunused;    int         i;    Size        totallen;    /*     * It's worth the trouble to be more paranoid here than in most places,     * because we are about to reshuffle data in (what is usually) a shared     * disk buffer.  If we aren't careful then corrupted pointers, lengths,     * etc could cause us to clobber adjacent disk buffers, spreading the data     * loss further.  So, check everything.     * 在这里比在其他地方执行更多的检查是值得的,因为我们将在(通常是)共享磁盘缓冲区中重新洗牌数据。     * 如果我们不小心,那么损坏的行指针、数据长度等可能会导致与相邻磁盘缓冲区冲突,     *   如果错误进一步传播会导致数据丢失。因此,需要仔细检查。     */    if (pd_lower < SizeOfPageHeaderData ||        pd_lower > pd_upper ||        pd_upper > pd_special ||        pd_special > BLCKSZ ||        pd_special != MAXALIGN(pd_special))        ereport(ERROR,                (errcode(ERRCODE_DATA_CORRUPTED),                 errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",                        pd_lower, pd_upper, pd_special)));    /*     * Run through the line pointer array and collect data about live items.     * 遍历行指针数组,收集存活的条目.     */    nline = PageGetMaxOffsetNumber(page);//获取最大的偏移    itemidptr = itemidbase;//    nunused = totallen = 0;    for (i = FirstOffsetNumber; i <= nline; i++)    {        //---------- 遍历行指针数组        //获取line pointer        lp = PageGetItemId(page, i);        if (ItemIdIsUsed(lp))        {            //如果ItemId在使用             if (ItemIdHasStorage(lp))            {                //如ItemID与存储相关,判断条件:((itemId)->lp_len != 0)                itemidptr->offsetindex = i - 1;                itemidptr->itemoff = ItemIdGetOffset(lp);                //执行判断                if (unlikely(itemidptr->itemoff < (int) pd_upper ||                             itemidptr->itemoff >= (int) pd_special))                    ereport(ERROR,                            (errcode(ERRCODE_DATA_CORRUPTED),                             errmsg("corrupted item pointer: %u",                                    itemidptr->itemoff)));                //对齐长度                itemidptr->alignedlen = MAXALIGN(ItemIdGetLength(lp));                totallen += itemidptr->alignedlen;                itemidptr++;//数组下一个元素            }        }        else        {            /* Unused entries should have lp_len = 0, but make sure */            //未使用的ItemId            ItemIdSetUnused(lp);            nunused++;        }    }    //数组中存储的元素个数    nstorage = itemidptr - itemidbase;    if (nstorage == 0)    {        /* Page is completely empty, so just reset it quickly */        //page完全是空的,重置page        ((PageHeader) page)->pd_upper = pd_special;    }    else    {        /* Need to compact the page the hard way */        //page非空,压缩页        if (totallen > (Size) (pd_special - pd_lower))            ereport(ERROR,                    (errcode(ERRCODE_DATA_CORRUPTED),                     errmsg("corrupted item lengths: total %u, available space %u",                            (unsigned int) totallen, pd_special - pd_lower)));        compactify_tuples(itemidbase, nstorage, page);    }    /* Set hint bit for PageAddItem */    //为PageAddItem方法设置标记位    if (nunused > 0)        //存在未使用的空位,设置标记        PageSetHasFreeLinePointers(page);    else        //清除标记        PageClearHasFreeLinePointers(page);}/* * After removing or marking some line pointers unused, move the tuples to * remove the gaps caused by the removed items. * 在清除或者标记某些行指针为没有使用后,移动元组以消除已删除元组之间的鸿沟 */static voidcompactify_tuples(itemIdSort itemidbase, int nitems, Page page){    PageHeader  phdr = (PageHeader) page;    Offset      upper;    int         i;    /* sort itemIdSortData array into decreasing itemoff order */    //以itemoff降序的方式排序itemIdSortData数组    qsort((char *) itemidbase, nitems, sizeof(itemIdSortData),          itemoffcompare);    //重整page    upper = phdr->pd_special;    for (i = 0; i < nitems; i++)    {        itemIdSort  itemidptr = &itemidbase[i];        ItemId      lp;        lp = PageGetItemId(page, itemidptr->offsetindex + 1);        upper -= itemidptr->alignedlen;        memmove((char *) page + upper,                (char *) page + itemidptr->itemoff,                itemidptr->alignedlen);        lp->lp_off = upper;    }    phdr->pd_upper = upper;}/* * ItemIdSetUnused *      Set the item identifier to be UNUSED, with no storage. *      Beware of multiple evaluations of itemId! *      设置ItemId为未使用. */#define ItemIdSetUnused(itemId) \( \    (itemId)->lp_flags = LP_UNUSED, \    (itemId)->lp_off = 0, \    (itemId)->lp_len = 0 \)

三、跟踪分析

测试脚本 : 删除数据,执行vacuum

11:04:59 (xdb@[local]:5432)testdb=# delete from t1 where id < 600;DELETE 10014:26:16 (xdb@[local]:5432)testdb=# checkpoint;CHECKPOINT11:18:29 (xdb@[local]:5432)testdb=# vacuum verbose t1;

lazy_vacuum_heap
启动gdb,设置断点

(gdb) b lazy_vacuum_heapBreakpoint 7 at 0x6bdf2e: file vacuumlazy.c, line 1472.(gdb) cContinuing.Breakpoint 7, lazy_vacuum_heap (onerel=0x7f4c70d96688, vacrelstats=0x1873928) at vacuumlazy.c:14721472        Buffer      vmbuffer = InvalidBuffer;(gdb)

输入参数
1-relation

(gdb) p *onerel$14 = {rd_node = {spcNode = 1663, dbNode = 16402, relNode = 50820}, rd_smgr = 0x18362e0, rd_refcnt = 1, rd_backend = -1,   rd_islocaltemp = false, rd_isnailed = false, rd_isvalid = true, rd_indexvalid = 1 '\001', rd_statvalid = false,   rd_createSubid = 0, rd_newRelfilenodeSubid = 0, rd_rel = 0x7f4c70d95bb8, rd_att = 0x7f4c70d95cd0, 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 = 0x7f4c70d94820, 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 = 0x182a030}

2-vacrelstats
存在索引,pages总数为124,扫描pages为124,原存活tuple为9501,新tuples为9401,已删除tuples为100,已删除的tuples的ItemPointer存储在dead_tuples数组中(大小为num_dead_tuples)

(gdb) p *vacrelstats$15 = {hasindex = true, old_rel_pages = 124, rel_pages = 124, scanned_pages = 124, pinskipped_pages = 0,   frozenskipped_pages = 0, tupcount_pages = 124, old_live_tuples = 9501, new_rel_tuples = 9401, new_live_tuples = 9401,   new_dead_tuples = 0, pages_removed = 0, tuples_deleted = 100, nonempty_pages = 124, num_dead_tuples = 100,   max_dead_tuples = 36084, dead_tuples = 0x1884820, num_index_scans = 0, latestRemovedXid = 397073,   lock_waiter_detected = false}(gdb)

1.初始化变量

(gdb) n1474        pg_rusage_init(&ru0);(gdb) 1475        npages = 0;(gdb) 1477        tupindex = 0;(gdb) p ru0$16 = {tv = {tv_sec = 1548743482, tv_usec = 626506}, ru = {ru_utime = {tv_sec = 0, tv_usec = 40060}, ru_stime = {      tv_sec = 0, tv_usec = 114769}, {ru_maxrss = 8900, __ru_maxrss_word = 8900}, {ru_ixrss = 0, __ru_ixrss_word = 0}, {      ru_idrss = 0, __ru_idrss_word = 0}, {ru_isrss = 0, __ru_isrss_word = 0}, {ru_minflt = 5455, __ru_minflt_word = 5455},     {ru_majflt = 0, __ru_majflt_word = 0}, {ru_nswap = 0, __ru_nswap_word = 0}, {ru_inblock = 2616,       __ru_inblock_word = 2616}, {ru_oublock = 376, __ru_oublock_word = 376}, {ru_msgsnd = 0, __ru_msgsnd_word = 0}, {      ru_msgrcv = 0, __ru_msgrcv_word = 0}, {ru_nsignals = 0, __ru_nsignals_word = 0}, {ru_nvcsw = 814,       __ru_nvcsw_word = 814}, {ru_nivcsw = 2, __ru_nivcsw_word = 2}}}

2.遍历vacrelstats->num_dead_tuples行指针数组(ItemPointer)

(gdb) n1478        while (tupindex < vacrelstats->num_dead_tuples)(gdb)

2.1获取块号/读取块到缓冲区中

1485            vacuum_delay_point();(gdb) 1487            tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);(gdb) 1488            buf = ReadBufferExtended(onerel, MAIN_FORKNUM, tblk, RBM_NORMAL,(gdb) (gdb) p tblk$17 = 29(gdb) p buf$18 = 175

2.2加锁,如不成功,则处理下一个元组

1490            if (!ConditionalLockBufferForCleanup(buf))(gdb)

2.3调用lazy_vacuum_page释放空间,整理碎片

1496            tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats,(gdb) p tupindex$1 = 0(gdb) n1500            page = BufferGetPage(buf);(gdb) p tupindex$2 = 2(gdb)

2.4获取page,获取该page的空闲空间

(gdb) n1500            page = BufferGetPage(buf);(gdb) p tupindex$2 = 2(gdb) n1501            freespace = PageGetHeapFreeSpace(page);(gdb)

2.5释放缓冲,记录空闲空间

(gdb) 1503            UnlockReleaseBuffer(buf);(gdb) 1504            RecordPageWithFreeSpace(onerel, tblk, freespace);(gdb) 1505            npages++;(gdb)

lazy_vacuum_page
进入lazy_vacuum_page函数

1496            tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats,(gdb) p tblk$3 = 30(gdb) p buf$4 = 178(gdb) p tupindex$5 = 2(gdb) (gdb) steplazy_vacuum_page (onerel=0x7f4c70d95570, blkno=30, buffer=178, tupindex=2, vacrelstats=0x18676a8, vmbuffer=0x7fffaef4a19c)    at vacuumlazy.c:15351535        Page        page = BufferGetPage(buffer);(gdb)

输入参数:块号/缓冲区编号/tuple数组下标以及vacrelstats(统计信息+辅助存储信息,如废弃元组数组等)

(gdb) p vacrelstats->dead_tuples[0]$6 = {ip_blkid = {bi_hi = 0, bi_lo = 29}, ip_posid = 168}

1.初始化相关变量

(gdb) n1537        int         uncnt = 0;(gdb) 1541        pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno);(gdb) 1543        START_CRIT_SECTION();(gdb) 1545        for (; tupindex < vacrelstats->num_dead_tuples; tupindex++)(gdb) p page$7 = (Page) 0x7f4c44f46380 "\001"(gdb) p *page$8 = 1 '\001'(gdb) p *(PageHeader *)page$9 = (PageHeader) 0x4ec2441800000001(gdb) p *(PageHeader)page$10 = {pd_lsn = {xlogid = 1, xrecoff = 1321354264}, pd_checksum = 0, pd_flags = 1, pd_lower = 1188, pd_upper = 7856,   pd_special = 8192, pd_pagesize_version = 8196, pd_prune_xid = 0, pd_linp = 0x7f4c44f46398}(gdb)

2.遍历废弃元组数组
2.1获取块号,如块号不一致,跳出循环
2.2获取偏移/行指针
2.3标记为未使用,记录偏移

(gdb) n1551            tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);(gdb) 1552            if (tblk != blkno)(gdb) p tblk$11 = 30(gdb) n1554            toff = ItemPointerGetOffsetNumber(&vacrelstats->dead_tuples[tupindex]);(gdb) p vacrelstats->dead_tuples[tupindex]$12 = {ip_blkid = {bi_hi = 0, bi_lo = 30}, ip_posid = 162}(gdb) n1555            itemid = PageGetItemId(page, toff);(gdb) p toff$13 = 162(gdb) n1556            ItemIdSetUnused(itemid);(gdb) p itemid$14 = (ItemId) 0x7f4c44f4661c(gdb) p *itemid$15 = {lp_off = 0, lp_flags = 3, lp_len = 0}(gdb) n1557            unused[uncnt++] = toff;(gdb) 1545        for (; tupindex < vacrelstats->num_dead_tuples; tupindex++)(gdb)

3.调用PageRepairFragmentation整理碎片
3.1判断和检查(严谨的编码!!!)

...(gdb) b vacuumlazy.c:1560Breakpoint 2 at 0x6be604: file vacuumlazy.c, line 1560.(gdb) cContinuing.Breakpoint 2, lazy_vacuum_page (onerel=0x7f4c70d95570, blkno=30, buffer=178, tupindex=5, vacrelstats=0x18676a8,     vmbuffer=0x7fffaef4a19c) at vacuumlazy.c:15601560        PageRepairFragmentation(page);(gdb) (gdb) stepPageRepairFragmentation (page=0x7f4c44f46380 "\001") at bufpage.c:481481     Offset      pd_lower = ((PageHeader) page)->pd_lower;(gdb) n482     Offset      pd_upper = ((PageHeader) page)->pd_upper;(gdb) 483     Offset      pd_special = ((PageHeader) page)->pd_special;(gdb) 500     if (pd_lower < SizeOfPageHeaderData ||(gdb) p pd_lower$17 = 1188(gdb) p pd_upper$18 = 7856(gdb) p pd_special$19 = 8192(gdb) n501         pd_lower > pd_upper ||(gdb) 502         pd_upper > pd_special ||(gdb) 504         pd_special != MAXALIGN(pd_special))(gdb) 503         pd_special > BLCKSZ ||

3.2获取偏移,初始化变量

(gdb) 513     nline = PageGetMaxOffsetNumber(page);(gdb) n514     itemidptr = itemidbase;(gdb) 515     nunused = totallen = 0;(gdb) p nline$20 = 291(gdb) p *itemidptr$21 = {offsetindex = 162, itemoff = 8144, alignedlen = 48}(gdb)

3.3遍历行指针数组
3.3.1获取行指针lp
3.3.2如ItemId正在使用,记录到itemidbase数组中;否则标记ItemId未被使用

(gdb) 516     for (i = FirstOffsetNumber; i <= nline; i++)(gdb) n519         if (ItemIdIsUsed(lp))(gdb) 539             ItemIdSetUnused(lp);(gdb) 540             nunused++;(gdb) 516     for (i = FirstOffsetNumber; i <= nline; i++)(gdb)

跳出循环,继续执行

516     for (i = FirstOffsetNumber; i <= nline; i++)(gdb) b bufpage.c:544Breakpoint 3 at 0x8b1d2d: file bufpage.c, line 544.(gdb) cContinuing.Breakpoint 3, PageRepairFragmentation (page=0x7f4c44f46380 "\001") at bufpage.c:544544     nstorage = itemidptr - itemidbase;(gdb) (gdb) p nunused$22 = 284

3.4计算数组中存储的元素个数
A.如个数为0,重置page
B.否则调用compactify_tuples压缩页

(gdb) n545     if (nstorage == 0)(gdb) p nstorage$23 = 7(gdb) n553         if (totallen > (Size) (pd_special - pd_lower))(gdb) 559         compactify_tuples(itemidbase, nstorage, page);(gdb)

3.5为PageAddItem方法设置标记位

(gdb) 563     if (nunused > 0)(gdb) 564         PageSetHasFreeLinePointers(page);(gdb) 567 }(gdb)

4.标记buffer为dirty

(gdb) lazy_vacuum_page (onerel=0x7f4c70d95570, blkno=30, buffer=178, tupindex=5, vacrelstats=0x18676a8, vmbuffer=0x7fffaef4a19c)    at vacuumlazy.c:15651565        MarkBufferDirty(buffer);(gdb) n

5.写入WAL Record

1568        if (RelationNeedsWAL(onerel))(gdb) 1572            recptr = log_heap_clean(onerel, buffer,(gdb) 1576            PageSetLSN(page, recptr);(gdb) 1585        END_CRIT_SECTION();

6.如all-visible,则设置页面all-visible标记

(gdb) n1593        if (heap_page_is_all_visible(onerel, buffer, &visibility_cutoff_xid,(gdb) 1595            PageSetAllVisible(page);(gdb)

7.如page为all-visible,设置vm

1602        if (PageIsAllVisible(page))(gdb) 1604            uint8       vm_status = visibilitymap_get_status(onerel, blkno, vmbuffer);(gdb) 1605            uint8       flags = 0;(gdb) 1608            if ((vm_status & VISIBILITYMAP_ALL_VISIBLE) == 0)(gdb) 1609                flags |= VISIBILITYMAP_ALL_VISIBLE;(gdb) 1610            if ((vm_status & VISIBILITYMAP_ALL_FROZEN) == 0 && all_frozen)(gdb) 1613            Assert(BufferIsValid(*vmbuffer));(gdb) 1614            if (flags != 0)(gdb) 1615                visibilitymap_set(onerel, blkno, buffer, InvalidXLogRecPtr,(gdb)

8.返回下一个page的起始数组编号

(gdb) 1619        return tupindex;(gdb) p tupindex$24 = 5(gdb)

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

标记 数组 页面 指针 偏移 空间 缓冲 碎片 处理 函数 数据 方法 空闲 变量 索引 缓冲区 存储 检查 个数 元素 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 什么是科技互联网 新华互联网科技学校 科技金融是不是互联网金融 科技互联网政务服务 有马互联网科技 互联网科技网站 互联网科技生活的内容 互联网系的金融科技公司 佳云科技和互联网广告 重庆互联网医院沙龙医盟信息科技 崇明区互联网教育科技售后服务 四川慕妮娅互联网科技有限公司 山东有哪些互联网科技公司 互联网相关科技技术 珠海东科互联网科技有限公司 湖北辰鑫科技互联网 美国五大互联网科技公司市值多少 唐山双旺合互联网科技产业园 互联网科技对化妆品行业 北京品多多互联网科技有限公司 互联网科技有限公司图片 互联网科技卖菜 猪兼强互联网科技 新浪科技互联网 互联网科技小额贷款 互联网科技有限公司 翻译 互联网科技公司的市场部门 博兴的互联网电子科技公司 互联网科技博主是什么意思 银川新华互联网科技学校联系电话
0