溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PostgreSQL的vacuum過程中l(wèi)azy_vacuum_heap函數(shù)有什么作用

發(fā)布時間:2021-11-09 16:17:20 來源:億速云 閱讀:189 作者:iii 欄目:關(guān)系型數(shù)據(jù)庫

這篇文章主要介紹“PostgreSQL的vacuum過程中l(wèi)azy_vacuum_heap函數(shù)有什么作用”,在日常操作中,相信很多人在PostgreSQL的vacuum過程中l(wèi)azy_vacuum_heap函數(shù)有什么作用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”PostgreSQL的vacuum過程中l(wèi)azy_vacuum_heap函數(shù)有什么作用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

本節(jié)簡單介紹了PostgreSQL手工執(zhí)行vacuum的處理流程,主要分析了ExecVacuum->vacuum->vacuum_rel->heap_vacuum_rel->lazy_scan_heap->lazy_vacuum_heap函數(shù)的實現(xiàn)邏輯,該函數(shù)訪問堆表,標記廢棄元組為未使用并在這些元組所在頁面上壓縮空閑空間。

一、數(shù)據(jù)結(jié)構(gòu)

宏定義
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.
 * 雖然在這里有兩種不同的語句,但只需要使用統(tǒng)一的Node類型即可.
 * 注意至少VACOPT_VACUUM/VACOPT_ANALYZE在選項中設(shè)置.
 * ----------------------
 */
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
{
    //行指針數(shù)組索引
    uint16      offsetindex;    /* linp array index */
    //item數(shù)據(jù)頁內(nèi)偏移
    int16       itemoff;        /* page offset of item data */
    //對齊長度
    uint16      alignedlen;     /* MAXALIGN(item data len) */
} itemIdSortData;
//結(jié)構(gòu)體指針
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的全局統(tǒng)計信息
    //pg_class.relpages的上一個值
    BlockNumber old_rel_pages;  /* previous value of pg_class.relpages */
    //pages的總數(shù)
    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 */
    //新估算的總元組數(shù)
    double      new_rel_tuples; /* new estimated total # of tuples */
    //新估算的存活元組數(shù)
    double      new_live_tuples;    /* new estimated total # of live tuples */
    //新估算的廢棄元組數(shù)
    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地址排序
    //當前的入口/條目數(shù)
    int         num_dead_tuples;    /* current # of entries */
    //數(shù)組中已分配的slots(最大已廢棄元組數(shù))
    int         max_dead_tuples;    /* # slots allocated in array */
    //ItemPointer數(shù)組
    ItemPointer dead_tuples;    /* array of ItemPointerData */
    //掃描的索引數(shù)
    int         num_index_scans;
    //最后被清除的事務(wù)ID
    TransactionId latestRemovedXid;
    //是否存在waiter?
    bool        lock_waiter_detected;
} LVRelStats;

ItemPointer
行指針

typedef struct ItemPointerData
{
    BlockIdData ip_blkid;//塊號
    OffsetNumber ip_posid;//塊內(nèi)偏移
}
typedef ItemPointerData *ItemPointer;

二、源碼解讀

lazy_vacuum_heap
lazy_vacuum_heap標記廢棄元組為未使用并在這些元組所在頁面上壓縮空閑空間,在此期間,不會訪問lazy_scan_heap標記為存活元組的頁面.
主要處理流程如下:
1.初始化變量
2.遍歷vacrelstats->num_dead_tuples行指針數(shù)組(ItemPointer)
2.1獲取塊號/讀取塊到緩沖區(qū)中
2.2加鎖,如不成功,則處理下一個元組
2.3調(diào)用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 void
lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
{
    int         tupindex;//元組索引
    int         npages;//頁面數(shù)
    PGRUsage    ru0;
    Buffer      vmbuffer = InvalidBuffer;//vm緩沖
    pg_rusage_init(&ru0);//初始化
    npages = 0;
    tupindex = 0;
    //遍歷廢棄元組
    //vacrelstats->dead_tuples數(shù)組中的元素類型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))
        {
            //獲取不了,釋放資源,跳轉(zhuǎn)到下一個元組
            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 */
        //現(xiàn)在已經(jīng)壓縮了頁面(釋放了空間),記錄可用空間
        page = BufferGetPage(buf);
        freespace = PageGetHeapFreeSpace(page);
        UnlockReleaseBuffer(buf);
        RecordPageWithFreeSpace(onerel, tblk, freespace);
        npages++;
    }
    if (BufferIsValid(vmbuffer))
    {
        //釋放緩沖區(qū)
        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.初始化相關(guān)變量
2.遍歷廢棄元組數(shù)組
2.1獲取塊號,如塊號不一致,跳出循環(huán)
2.2獲取偏移/行指針
2.3標記為未使用,記錄偏移
3.調(diào)用PageRepairFragmentation整理碎片
3.1判斷和檢查(嚴謹?shù)木幋a!!!)
3.2獲取偏移,初始化變量
3.3遍歷行指針數(shù)組
3.3.1獲取行指針lp
3.3.2如ItemId正在使用,記錄到itemidbase數(shù)組中;否則標記ItemId未被使用
3.4計算數(shù)組中存儲的元素個數(shù)
 A.如個數(shù)為0,重置page
 B.否則調(diào)用compactify_tuples壓縮頁
3.5為PageAddItem方法設(shè)置標記位
4.標記buffer為dirty
5.寫入WAL Record
6.如all-visible,則設(shè)置頁面all-visible標記
7.如page為all-visible,設(shè)置vm
8.返回下一個page的起始數(shù)組編號

/*
 *  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.
 * 調(diào)用者必須持有buffer的pin和cleanup鎖才能執(zhí)行
 *
 * 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 int
lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
                 int tupindex, LVRelStats *vacrelstats, Buffer *vmbuffer)
{
    //獲取page
    Page        page = BufferGetPage(buffer);
    OffsetNumber unused[MaxOffsetNumber];//偏移數(shù)組
    int         uncnt = 0;
    TransactionId visibility_cutoff_xid;//事務(wù)ID
    bool        all_frozen;//釋放全部凍結(jié)
    pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno);
    //進入關(guān)鍵處理部分
    START_CRIT_SECTION();
    //遍歷廢棄元組數(shù)組
    for (; tupindex < vacrelstats->num_dead_tuples; tupindex++)
    {
        BlockNumber tblk;//塊號
        OffsetNumber toff;//偏移
        ItemId      itemid;//行指針
        //根據(jù)行指針獲取塊號
        tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);
        if (tblk != blkno)
            //不是同一個塊,跳出循環(huán)
            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.
     * 結(jié)束關(guān)鍵區(qū)域,這樣我們可以安全的執(zhí)行可見性檢查
     * (這可能需要執(zhí)行IO/分配內(nèi)存)
     * 如果進程崩潰,頁面(包括相應(yīng)的vm位)可能標記為all-visible,但這也沒有問題,后續(xù)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.
     * 現(xiàn)在,我們已經(jīng)從頁面中刪除了廢棄的元組,再次檢查頁面是否已經(jīng)全部可見。
     * 頁面已經(jīng)被標記為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.
     * 堆頁面的所有修改已完成.如果設(shè)置了all-visible標記,同時設(shè)置VM all-visible位
     * (而且,如可能,設(shè)置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 */
        //如需要,設(shè)置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標記位會被更新.
 * 
 */
void
PageRepairFragmentation(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];//存儲數(shù)據(jù)
    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.
     * 在這里比在其他地方執(zhí)行更多的檢查是值得的,因為我們將在(通常是)共享磁盤緩沖區(qū)中重新洗牌數(shù)據(jù)。
     * 如果我們不小心,那么損壞的行指針、數(shù)據(jù)長度等可能會導致與相鄰磁盤緩沖區(qū)沖突,
     *   如果錯誤進一步傳播會導致數(shù)據(jù)丟失。因此,需要仔細檢查。
     */
    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.
     * 遍歷行指針數(shù)組,收集存活的條目.
     */
    nline = PageGetMaxOffsetNumber(page);//獲取最大的偏移
    itemidptr = itemidbase;//
    nunused = totallen = 0;
    for (i = FirstOffsetNumber; i <= nline; i++)
    {
        //---------- 遍歷行指針數(shù)組
        //獲取line pointer
        lp = PageGetItemId(page, i);
        if (ItemIdIsUsed(lp))
        {
            //如果ItemId在使用 
            if (ItemIdHasStorage(lp))
            {
                //如ItemID與存儲相關(guān),判斷條件:((itemId)->lp_len != 0)
                itemidptr->offsetindex = i - 1;
                itemidptr->itemoff = ItemIdGetOffset(lp);
                //執(zhí)行判斷
                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++;//數(shù)組下一個元素
            }
        }
        else
        {
            /* Unused entries should have lp_len = 0, but make sure */
            //未使用的ItemId
            ItemIdSetUnused(lp);
            nunused++;
        }
    }
    //數(shù)組中存儲的元素個數(shù)
    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方法設(shè)置標記位
    if (nunused > 0)
        //存在未使用的空位,設(shè)置標記
        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 void
compactify_tuples(itemIdSort itemidbase, int nitems, Page page)
{
    PageHeader  phdr = (PageHeader) page;
    Offset      upper;
    int         i;
    /* sort itemIdSortData array into decreasing itemoff order */
    //以itemoff降序的方式排序itemIdSortData數(shù)組
    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!
 *      設(shè)置ItemId為未使用.
 */
#define ItemIdSetUnused(itemId) \
( \
    (itemId)->lp_flags = LP_UNUSED, \
    (itemId)->lp_off = 0, \
    (itemId)->lp_len = 0 \
)

三、跟蹤分析

測試腳本 : 刪除數(shù)據(jù),執(zhí)行vacuum

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

lazy_vacuum_heap
啟動gdb,設(shè)置斷點

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

輸入?yún)?shù)
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總數(shù)為124,掃描pages為124,原存活tuple為9501,新tuples為9401,已刪除tuples為100,已刪除的tuples的ItemPointer存儲在dead_tuples數(shù)組中(大小為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) n
1474        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行指針數(shù)組(ItemPointer)

(gdb) n
1478        while (tupindex < vacrelstats->num_dead_tuples)
(gdb)

2.1獲取塊號/讀取塊到緩沖區(qū)中

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調(diào)用lazy_vacuum_page釋放空間,整理碎片

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

2.4獲取page,獲取該page的空閑空間

(gdb) n
1500            page = BufferGetPage(buf);
(gdb) p tupindex
$2 = 2
(gdb) n
1501            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函數(shù)

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) step
lazy_vacuum_page (onerel=0x7f4c70d95570, blkno=30, buffer=178, tupindex=2, vacrelstats=0x18676a8, vmbuffer=0x7fffaef4a19c)
    at vacuumlazy.c:1535
1535        Page        page = BufferGetPage(buffer);
(gdb)

輸入?yún)?shù):塊號/緩沖區(qū)編號/tuple數(shù)組下標以及vacrelstats(統(tǒng)計信息+輔助存儲信息,如廢棄元組數(shù)組等)

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

1.初始化相關(guān)變量

(gdb) n
1537        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.遍歷廢棄元組數(shù)組
2.1獲取塊號,如塊號不一致,跳出循環(huán)
2.2獲取偏移/行指針
2.3標記為未使用,記錄偏移

(gdb) n
1551            tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);
(gdb) 
1552            if (tblk != blkno)
(gdb) p tblk
$11 = 30
(gdb) n
1554            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) n
1555            itemid = PageGetItemId(page, toff);
(gdb) p toff
$13 = 162
(gdb) n
1556            ItemIdSetUnused(itemid);
(gdb) p itemid
$14 = (ItemId) 0x7f4c44f4661c
(gdb) p *itemid
$15 = {lp_off = 0, lp_flags = 3, lp_len = 0}
(gdb) n
1557            unused[uncnt++] = toff;
(gdb) 
1545        for (; tupindex < vacrelstats->num_dead_tuples; tupindex++)
(gdb)

3.調(diào)用PageRepairFragmentation整理碎片
3.1判斷和檢查(嚴謹?shù)木幋a!!!)

...
(gdb) b vacuumlazy.c:1560
Breakpoint 2 at 0x6be604: file vacuumlazy.c, line 1560.
(gdb) c
Continuing.
Breakpoint 2, lazy_vacuum_page (onerel=0x7f4c70d95570, blkno=30, buffer=178, tupindex=5, vacrelstats=0x18676a8, 
    vmbuffer=0x7fffaef4a19c) at vacuumlazy.c:1560
1560        PageRepairFragmentation(page);
(gdb) 
(gdb) step
PageRepairFragmentation (page=0x7f4c44f46380 "\001") at bufpage.c:481
481     Offset      pd_lower = ((PageHeader) page)->pd_lower;
(gdb) n
482     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) n
501         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) n
514     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遍歷行指針數(shù)組
3.3.1獲取行指針lp
3.3.2如ItemId正在使用,記錄到itemidbase數(shù)組中;否則標記ItemId未被使用

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

跳出循環(huán),繼續(xù)執(zhí)行

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

3.4計算數(shù)組中存儲的元素個數(shù)
 A.如個數(shù)為0,重置page
 B.否則調(diào)用compactify_tuples壓縮頁

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

3.5為PageAddItem方法設(shè)置標記位

(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:1565
1565        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,則設(shè)置頁面all-visible標記

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

7.如page為all-visible,設(shè)置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的起始數(shù)組編號

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

到此,關(guān)于“PostgreSQL的vacuum過程中l(wèi)azy_vacuum_heap函數(shù)有什么作用”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI