这篇文章主要介绍“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命令选项

/*----------------------*VacuumandAnalyzeStatements*Vacuum和Analyze命令选项**Eventhoughthesearenominallytwostatements,it'sconvenienttouse*justonenodetypeforboth.NotethatatleastoneofVACOPT_VACUUM*andVACOPT_ANALYZEmustbesetinoptions.*虽然在这里有两种不同的语句,但只需要使用统一的Node类型即可.*注意至少VACOPT_VACUUM/VACOPT_ANALYZE在选项中设置.*----------------------*/typedefenumVacuumOption{VACOPT_VACUUM=1<<0,/*doVACUUM*/VACOPT_ANALYZE=1<<1,/*doANALYZE*/VACOPT_VERBOSE=1<<2,/*printprogressinfo*/VACOPT_FREEZE=1<<3,/*FREEZEoption*/VACOPT_FULL=1<<4,/*FULL(non-concurrent)vacuum*/VACOPT_SKIP_LOCKED=1<<5,/*skipifcannotgetlock*/VACOPT_SKIPTOAST=1<<6,/*don'tprocesstheTOASTtable,ifany*/VACOPT_DISABLE_PAGE_SKIPPING=1<<7/*don'tskipanypages*/}VacuumOption;

itemIdSort
PageRepairFragmentation/PageIndexMultiDelete的排序支持

/**sortingsupportforPageRepairFragmentationandPageIndexMultiDelete*PageRepairFragmentation/PageIndexMultiDelete的排序支持*/typedefstructitemIdSortData{//行指针数组索引uint16offsetindex;/*linparrayindex*///item数据页内偏移int16itemoff;/*pageoffsetofitemdata*///对齐长度uint16alignedlen;/*MAXALIGN(itemdatalen)*/}itemIdSortData;//结构体指针typedefitemIdSortData*itemIdSort;

LVRelStats

typedefstructLVRelStats{/*hasindex=truemeanstwo-passstrategy;falsemeansone-pass*///T表示two-passstrategy,F表示one-passstrategyboolhasindex;/*Overallstatisticsaboutrel*///rel的全局统计信息//pg_class.relpages的上一个值BlockNumberold_rel_pages;/*previousvalueofpg_class.relpages*///pages的总数BlockNumberrel_pages;/*totalnumberofpages*///扫描的pagesBlockNumberscanned_pages;/*numberofpagesweexamined*///由于pin跳过的pagesBlockNumberpinskipped_pages;/*#ofpagesweskippedduetoapin*///跳过的frozenpagesBlockNumberfrozenskipped_pages;/*#offrozenpagesweskipped*///计算其元组的pagesBlockNumbertupcount_pages;/*pageswhosetupleswecounted*///pg_class.reltuples的前值doubleold_live_tuples;/*previousvalueofpg_class.reltuples*///新估算的总元组数doublenew_rel_tuples;/*newestimatedtotal#oftuples*///新估算的存活元组数doublenew_live_tuples;/*newestimatedtotal#oflivetuples*///新估算的废弃元组数doublenew_dead_tuples;/*newestimatedtotal#ofdeadtuples*///已清除的pagesBlockNumberpages_removed;//已删除的tuplesdoubletuples_deleted;//实际上是非空page+1BlockNumbernonempty_pages;/*actually,lastnonemptypage+1*//*ListofTIDsoftuplesweintendtodelete*//*NB:thislistisorderedbyTIDaddress*///将要删除的元组TIDs链表//注意:该链表已使用TID地址排序//当前的入口/条目数intnum_dead_tuples;/*current#ofentries*///数组中已分配的slots(最大已废弃元组数)intmax_dead_tuples;/*#slotsallocatedinarray*///ItemPointer数组ItemPointerdead_tuples;/*arrayofItemPointerData*///扫描的索引数intnum_index_scans;//最后被清除的事务IDTransactionIdlatestRemovedXid;//是否存在waiter?boollock_waiter_detected;}LVRelStats;

ItemPointer
行指针

typedefstructItemPointerData{BlockIdDataip_blkid;//块号OffsetNumberip_posid;//块内偏移}typedefItemPointerData*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()--secondpassovertheheap*lazy_vacuum_heap()--二次访问堆表**Thisroutinemarksdeadtuplesasunusedandcompactsoutfree*spaceontheirpages.Pagesnothavingdeadtuplesrecordedfrom*lazy_scan_heaparenotvisitedatall.*lazy_vacuum_heap标记废弃元组为未使用并在这些元组所在页面上压缩空闲空间.*在此期间,不会访问lazy_scan_heap标记没有废弃元组的页面.**Note:thereasonfordoingthisasasecondpassiswecannotremove*thetuplesuntilwe'veremovedtheirindexentries,andwewantto*processindexentryremovalinbatchesaslargeaspossible.*注意:二次访问堆表的原因是在清除索引条目前不能清除元组,*而且我们希望以批量的方式处理索引条目,越大越好.*/staticvoidlazy_vacuum_heap(Relationonerel,LVRelStats*vacrelstats){inttupindex;//元组索引intnpages;//页面数PGRUsageru0;Buffervmbuffer=InvalidBuffer;//vm缓冲pg_rusage_init(&ru0);//初始化npages=0;tupindex=0;//遍历废弃元组//vacrelstats->dead_tuples数组中的元素类型ItemPointerwhile(tupindex<vacrelstats->num_dead_tuples){BlockNumbertblk;//块号Bufferbuf;//缓冲Pagepage;//页面Sizefreespace;vacuum_delay_point();//获取块号tblk=ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);//以扩展方式读取bufferbuf=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);/*Nowthatwe'vecompactedthepage,recorditsavailablespace*///现在已经压缩了页面(释放了空间),记录可用空间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%drowversionsin%dpages",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()--freedeadtuplesonapage*andrepairitsfragmentation.*lazy_vacuum_page()--释放page中的废弃元组,并整理碎片**Callermustholdpinandbuffercleanuplockonthebuffer.*调用者必须持有buffer的pin和cleanup锁才能执行**tupindexistheindexinvacrelstats->dead_tuplesofthefirstdead*tupleforthispage.Weassumetherestfollowsequentially.*Thereturnvalueisthefirsttupindexafterthetuplesofthispage.*tupindex是该page中第一个废弃元组在vacrelstats->dead_tuples中的编号,我们假定余下元组是顺序的.*返回值是该page中的元组后的第一个编号tupindex.*/staticintlazy_vacuum_page(Relationonerel,BlockNumberblkno,Bufferbuffer,inttupindex,LVRelStats*vacrelstats,Buffer*vmbuffer){//获取pagePagepage=BufferGetPage(buffer);OffsetNumberunused[MaxOffsetNumber];//偏移数组intuncnt=0;TransactionIdvisibility_cutoff_xid;//事务IDboolall_frozen;//释放全部冻结pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED,blkno);//进入关键处理部分START_CRIT_SECTION();//遍历废弃元组数组for(;tupindex<vacrelstats->num_dead_tuples;tupindex++){BlockNumbertblk;//块号OffsetNumbertoff;//偏移ItemIditemid;//行指针//根据行指针获取块号tblk=ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);if(tblk!=blkno)//不是同一个块,跳出循环break;/*pastendoftuplesforthisblock*///获取偏移toff=ItemPointerGetOffsetNumber(&vacrelstats->dead_tuples[tupindex]);//获取行指针itemid=PageGetItemId(page,toff);//标记为未使用ItemIdSetUnused(itemid);//记录偏移unused[uncnt++]=toff;}//整理碎片PageRepairFragmentation(page);/**MarkbufferdirtybeforewewriteWAL.*标记buffer为dirty*/MarkBufferDirty(buffer);/*XLOGstuff*/if(RelationNeedsWAL(onerel)){//记录WALRecordXLogRecPtrrecptr;recptr=log_heap_clean(onerel,buffer,NULL,0,NULL,0,unused,uncnt,vacrelstats->latestRemovedXid);PageSetLSN(page,recptr);}/**Endcriticalsection,sowesafelycandovisibilitytests(which*possiblyneedtoperformIOandallocatememory!).Ifwecrashnowthe*page(includingthecorrespondingvmbit)mightnotbemarkedall*visible,butthat'sfine.Alatervacuumwillfixthat.*结束关键区域,这样我们可以安全的执行可见性检查*(这可能需要执行IO/分配内存)*如果进程崩溃,页面(包括相应的vm位)可能标记为all-visible,但这也没有问题,后续vacuum会修复.*/END_CRIT_SECTION();/**Nowthatwehaveremovedthedeadtuplesfromthepage,onceagain*checkifthepagehasbecomeall-visible.Thepageisalreadymarked*dirty,exclusivelylocked,and,ifneeded,afullpageimagehasbeen*emittedinthelog_heap_clean()above.*现在,我们已经从页面中删除了废弃的元组,再次检查页面是否已经全部可见。*页面已经被标记为dirty、独占锁定,如需要,还会在log_heap_clean()中记录完整的页面镜像。*/if(heap_page_is_all_visible(onerel,buffer,&visibility_cutoff_xid,&all_frozen))PageSetAllVisible(page);/**Allthechangestotheheappagehavebeendone.Iftheall-visible*flagisnowset,alsosettheVMall-visiblebit(and,ifpossible,the*all-frozenbit)unlessthishasalreadybeendonepreviously.*堆页面的所有修改已完成.如果设置了all-visible标记,同时设置VMall-visible位*(而且,如可能,设置all-frozen位),除非先前已完成.*/if(PageIsAllVisible(page)){uint8vm_status=visibilitymap_get_status(onerel,blkno,vmbuffer);uint8flags=0;/*SettheVMall-frozenbittoflag,ifneeded*///如需要,设置VMall-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);}returntupindex;}/**PageRepairFragmentation**Freesfragmentedspaceonapage.*释放页面上的碎片空间.**Itdoesn'tremoveunusedlinepointers!Pleasedon'tchangethis.*该方法不会清楚未使用的行指针!因此,不要修改它.**Thisroutineisusableforheappagesonly,butseePageIndexMultiDelete.*该方法只用于堆页面,但注意参考PageIndexMultiDelete.**Asasideeffect,thepage'sPD_HAS_FREE_LINEShintbitisupdated.*该方法在处理的时候,页面的PD_HAS_FREE_LINES标记位会被更新.**/voidPageRepairFragmentation(Pagepage){Offsetpd_lower=((PageHeader)page)->pd_lower;Offsetpd_upper=((PageHeader)page)->pd_upper;Offsetpd_special=((PageHeader)page)->pd_special;itemIdSortDataitemidbase[MaxHeapTuplesPerPage];//存储数据itemIdSortitemidptr;ItemIdlp;intnline,nstorage,nunused;inti;Sizetotallen;/**It'sworththetroubletobemoreparanoidherethaninmostplaces,*becauseweareabouttoreshuffledatain(whatisusually)ashared*diskbuffer.Ifwearen'tcarefulthencorruptedpointers,lengths,*etccouldcauseustoclobberadjacentdiskbuffers,spreadingthedata*lossfurther.So,checkeverything.*在这里比在其他地方执行更多的检查是值得的,因为我们将在(通常是)共享磁盘缓冲区中重新洗牌数据。*如果我们不小心,那么损坏的行指针、数据长度等可能会导致与相邻磁盘缓冲区冲突,*如果错误进一步传播会导致数据丢失。因此,需要仔细检查。*/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("corruptedpagepointers:lower=%u,upper=%u,special=%u",pd_lower,pd_upper,pd_special)));/**Runthroughthelinepointerarrayandcollectdataaboutliveitems.*遍历行指针数组,收集存活的条目.*/nline=PageGetMaxOffsetNumber(page);//获取最大的偏移itemidptr=itemidbase;//nunused=totallen=0;for(i=FirstOffsetNumber;i<=nline;i++){//----------遍历行指针数组//获取linepointerlp=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("corrupteditempointer:%u",itemidptr->itemoff)));//对齐长度itemidptr->alignedlen=MAXALIGN(ItemIdGetLength(lp));totallen+=itemidptr->alignedlen;itemidptr++;//数组下一个元素}}else{/*Unusedentriesshouldhavelp_len=0,butmakesure*///未使用的ItemIdItemIdSetUnused(lp);nunused++;}}//数组中存储的元素个数nstorage=itemidptr-itemidbase;if(nstorage==0){/*Pageiscompletelyempty,sojustresetitquickly*///page完全是空的,重置page((PageHeader)page)->pd_upper=pd_special;}else{/*Needtocompactthepagethehardway*///page非空,压缩页if(totallen>(Size)(pd_special-pd_lower))ereport(ERROR,(errcode(ERRCODE_DATA_CORRUPTED),errmsg("corrupteditemlengths:total%u,availablespace%u",(unsignedint)totallen,pd_special-pd_lower)));compactify_tuples(itemidbase,nstorage,page);}/*SethintbitforPageAddItem*///为PageAddItem方法设置标记位if(nunused>0)//存在未使用的空位,设置标记PageSetHasFreeLinePointers(page);else//清除标记PageClearHasFreeLinePointers(page);}/**Afterremovingormarkingsomelinepointersunused,movethetuplesto*removethegapscausedbytheremoveditems.*在清除或者标记某些行指针为没有使用后,移动元组以消除已删除元组之间的鸿沟*/staticvoidcompactify_tuples(itemIdSortitemidbase,intnitems,Pagepage){PageHeaderphdr=(PageHeader)page;Offsetupper;inti;/*sortitemIdSortDataarrayintodecreasingitemofforder*///以itemoff降序的方式排序itemIdSortData数组qsort((char*)itemidbase,nitems,sizeof(itemIdSortData),itemoffcompare);//重整pageupper=phdr->pd_special;for(i=0;i<nitems;i++){itemIdSortitemidptr=&itemidbase[i];ItemIdlp;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*SettheitemidentifiertobeUNUSED,withnostorage.*BewareofmultipleevaluationsofitemId!*设置ItemId为未使用.*/#defineItemIdSetUnused(itemId)\(\(itemId)->lp_flags=LP_UNUSED,\(itemId)->lp_off=0,\(itemId)->lp_len=0\)三、跟踪分析

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

11:04:59(xdb@[local]:5432)testdb=#deletefromt1whereid<600;DELETE10014:26:16(xdb@[local]:5432)testdb=#checkpoint;CHECKPOINT11:18:29(xdb@[local]:5432)testdb=#vacuumverboset1;

lazy_vacuum_heap
启动gdb,设置断点

(gdb)blazy_vacuum_heapBreakpoint7at0x6bdf2e:filevacuumlazy.c,line1472.(gdb)cContinuing.Breakpoint7,lazy_vacuum_heap(onerel=0x7f4c70d96688,vacrelstats=0x1873928)atvacuumlazy.c:14721472Buffervmbuffer=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)n1474pg_rusage_init(&ru0);(gdb)1475npages=0;(gdb)1477tupindex=0;(gdb)pru0$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)n1478while(tupindex<vacrelstats->num_dead_tuples)(gdb)

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

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

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

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

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

1496tupindex=lazy_vacuum_page(onerel,tblk,buf,tupindex,vacrelstats,(gdb)ptupindex$1=0(gdb)n1500page=BufferGetPage(buf);(gdb)ptupindex$2=2(gdb)

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

(gdb)n1500page=BufferGetPage(buf);(gdb)ptupindex$2=2(gdb)n1501freespace=PageGetHeapFreeSpace(page);(gdb)

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

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

lazy_vacuum_page
进入lazy_vacuum_page函数

1496tupindex=lazy_vacuum_page(onerel,tblk,buf,tupindex,vacrelstats,(gdb)ptblk$3=30(gdb)pbuf$4=178(gdb)ptupindex$5=2(gdb)(gdb)steplazy_vacuum_page(onerel=0x7f4c70d95570,blkno=30,buffer=178,tupindex=2,vacrelstats=0x18676a8,vmbuffer=0x7fffaef4a19c)atvacuumlazy.c:15351535Pagepage=BufferGetPage(buffer);(gdb)

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

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

1.初始化相关变量

(gdb)n1537intuncnt=0;(gdb)1541pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED,blkno);(gdb)1543START_CRIT_SECTION();(gdb)1545for(;tupindex<vacrelstats->num_dead_tuples;tupindex++)(gdb)ppage$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)n1551tblk=ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);(gdb)1552if(tblk!=blkno)(gdb)ptblk$11=30(gdb)n1554toff=ItemPointerGetOffsetNumber(&vacrelstats->dead_tuples[tupindex]);(gdb)pvacrelstats->dead_tuples[tupindex]$12={ip_blkid={bi_hi=0,bi_lo=30},ip_posid=162}(gdb)n1555itemid=PageGetItemId(page,toff);(gdb)ptoff$13=162(gdb)n1556ItemIdSetUnused(itemid);(gdb)pitemid$14=(ItemId)0x7f4c44f4661c(gdb)p*itemid$15={lp_off=0,lp_flags=3,lp_len=0}(gdb)n1557unused[uncnt++]=toff;(gdb)1545for(;tupindex<vacrelstats->num_dead_tuples;tupindex++)(gdb)

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

...(gdb)bvacuumlazy.c:1560Breakpoint2at0x6be604:filevacuumlazy.c,line1560.(gdb)cContinuing.Breakpoint2,lazy_vacuum_page(onerel=0x7f4c70d95570,blkno=30,buffer=178,tupindex=5,vacrelstats=0x18676a8,vmbuffer=0x7fffaef4a19c)atvacuumlazy.c:15601560PageRepairFragmentation(page);(gdb)(gdb)stepPageRepairFragmentation(page=0x7f4c44f46380"\001")atbufpage.c:481481Offsetpd_lower=((PageHeader)page)->pd_lower;(gdb)n482Offsetpd_upper=((PageHeader)page)->pd_upper;(gdb)483Offsetpd_special=((PageHeader)page)->pd_special;(gdb)500if(pd_lower<SizeOfPageHeaderData||(gdb)ppd_lower$17=1188(gdb)ppd_upper$18=7856(gdb)ppd_special$19=8192(gdb)n501pd_lower>pd_upper||(gdb)502pd_upper>pd_special||(gdb)504pd_special!=MAXALIGN(pd_special))(gdb)503pd_special>BLCKSZ||

3.2获取偏移,初始化变量

(gdb)513nline=PageGetMaxOffsetNumber(page);(gdb)n514itemidptr=itemidbase;(gdb)515nunused=totallen=0;(gdb)pnline$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)516for(i=FirstOffsetNumber;i<=nline;i++)(gdb)n519if(ItemIdIsUsed(lp))(gdb)539ItemIdSetUnused(lp);(gdb)540nunused++;(gdb)516for(i=FirstOffsetNumber;i<=nline;i++)(gdb)

跳出循环,继续执行

516for(i=FirstOffsetNumber;i<=nline;i++)(gdb)bbufpage.c:544Breakpoint3at0x8b1d2d:filebufpage.c,line544.(gdb)cContinuing.Breakpoint3,PageRepairFragmentation(page=0x7f4c44f46380"\001")atbufpage.c:544544nstorage=itemidptr-itemidbase;(gdb)(gdb)pnunused$22=284

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

(gdb)n545if(nstorage==0)(gdb)pnstorage$23=7(gdb)n553if(totallen>(Size)(pd_special-pd_lower))(gdb)559compactify_tuples(itemidbase,nstorage,page);(gdb)

3.5为PageAddItem方法设置标记位

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

4.标记buffer为dirty

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

5.写入WAL Record

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

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

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

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

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

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

(gdb)1619returntupindex;(gdb)ptupindex$24=5(gdb)

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