这篇文章主要讲解了“PostgreSQL中heap_insert函数有什么作用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PostgreSQL中heap_insert函数有什么作用”吧!

一、数据结构

宏定义
包括Pointer/Page/XLOG_HEAP_INSERT/XLogRecPtr等

typedefchar*Pointer;//指针typedefPointerPage;//Page#defineXLOG_HEAP_INSERT0x00/**PointertoalocationintheXLOG.Thesepointersare64bitswide,*becausewedon'twantthemevertooverflow.*指向XLOG中的位置.*这些指针大小为64bit,以确保指针不会溢出.*/typedefuint64XLogRecPtr;/**Additionalmacrosforaccesstopageheaders.(Bewaremultipleevaluation*ofthearguments!)*/#definePageGetLSN(page)\PageXLogRecPtrGet(((PageHeader)(page))->pd_lsn)#definePageSetLSN(page,lsn)\PageXLogRecPtrSet(((PageHeader)(page))->pd_lsn,lsn)

xl_heap_insert
插入时需要获知的信息结构

/**xl_heap_insert/xl_heap_multi_insertflagvalues,8bitsareavailable.*//*PD_ALL_VISIBLEwascleared*/#defineXLH_INSERT_ALL_VISIBLE_CLEARED(1<<0)#defineXLH_INSERT_LAST_IN_MULTI(1<<1)#defineXLH_INSERT_IS_SPECULATIVE(1<<2)#defineXLH_INSERT_CONTAINS_NEW_TUPLE(1<<3)/*Thisiswhatweneedtoknowaboutinsert*///这是在插入时需要获知的信息typedefstructxl_heap_insert{//元组在page中的偏移OffsetNumberoffnum;/*insertedtuple'soffset*/uint8flags;//标记位/*xl_heap_header&TUPLEDATAinbackupblock0*///xl_heap_header&TUPLEDATA在备份块0中}xl_heap_insert;//xl_heap_insert大小#defineSizeOfHeapInsert(offsetof(xl_heap_insert,flags)+sizeof(uint8))

xl_heap_header
PG不会在WAL中存储插入/更新的元组的全部固定部分(HeapTupleHeaderData)
xl_heap_header是必须存储的结构.

/**Wedon'tstorethewholefixedpart(HeapTupleHeaderData)ofaninserted*orupdatedtupleinWAL;wecansaveafewbytesbyreconstructingthe*fieldsthatareavailableelsewhereintheWALrecord,orperhapsjust*plainneedn'tbereconstructed.Thesearethefieldswemuststore.*NOTE:t_hoffcouldberecomputed,butwemayaswellstoreitbecause*itwillcomeforfreeduetoalignmentconsiderations.*PG不会在WAL中存储插入/更新的元组的全部固定部分(HeapTupleHeaderData);*我们可以通过重新构造在WAL记录中可用的一些字段来节省一些空间,或者直接扁平化处理。*这些都是我们必须存储的字段。*注意:t_hoff可以重新计算,但我们也需要存储它,因为出于对齐的考虑,会被析构。*/typedefstructxl_heap_header{uint16t_infomask2;//t_infomask2标记uint16t_infomask;//t_infomask标记uint8t_hoff;//t_hoff}xl_heap_header;//HeapHeader的大小#defineSizeOfHeapHeader(offsetof(xl_heap_header,t_hoff)+sizeof(uint8))二、源码解读

heap_insert的主要逻辑是插入元组到堆中,其中存在对WAL(XLog)进行处理的部分.

/**heap_insert-inserttupleintoaheap*插入元组到堆中**ThenewtupleisstampedwithcurrenttransactionIDandthespecified*commandID.*新元组使用当前事务ID和指定的命令ID标记。**IftheHEAP_INSERT_SKIP_WALoptionisspecified,thenewtupleisnot*loggedinWAL,evenforanon-temprelation.Safeusageofthisbehavior*requiresthatwearrangethatallnewtuplesgointonewpagesnot*containinganytuplesfromothertransactions,andthattherelationgets*fsync'dbeforecommit.(Seealsoheap_sync()comments)*如果指定了HEAP_INSERT_SKIP_WAL选项,那么新的元组就不会记录到WAL中,*即使对于非临时关系也是如此。*如希望安全使用此选项,要求我们组织所有的新元组写入不包含来自其他事务的其他元组的新页面,*并且关系在提交之前得到fsync。(请参阅heap_sync()函数的注释)**TheHEAP_INSERT_SKIP_FSMoptionispasseddirectlyto*RelationGetBufferForTuple,whichseeformoreinfo.*HEAP_INSERT_SKIP_FSM选项作为参数直接传给RelationGetBufferForTuple**HEAP_INSERT_FROZENshouldonlybespecifiedforinsertsinto*relfilenodescreatedduringthecurrentsubtransactionandwhen*therearenopriorsnapshotsorpre-existingportalsopen.*Thiscausesrowstobefrozen,whichisanMVCCviolationand*requiresexplicitoptionschosenbyuser.*HEAP_INSERT_FROZEN应该只针对在当前子事务中创建的relfilenodes插入,*以及在没有打开以前的snapshots或已经存在的portals时指定。*这会导致行冻结,这是一种违反MVCC的行为,需要用户选择显式选项。**HEAP_INSERT_SPECULATIVEisusedonso-called"speculativeinsertions",*whichcanbebackedoutafterwardswithoutabortingthewholetransaction.*Othersessionscanwaitforthespeculativeinsertiontobeconfirmed,*turningitintoaregulartuple,oraborted,asifitneverexisted.*Speculativelyinsertedtuplesbehaveas"valuelocks"ofshortduration,*usedtoimplementINSERT..ONCONFLICT.*HEAP_INSERT_SPECULATIVE用于所谓的“投机性插入speculativeinsertions”,*这些插入可以在不中止整个事务的情况下在事后退出。*其他会话可以等待投机性插入得到确认,将其转换为常规元组,*或者中止,就像它不存在一样。*Speculatively插入的元组表现为短期的“值锁”,用于实现INSERT..ONCONFLICT。**Notethatmostoftheseoptionswillbeappliedwheninsertingintothe*heap'sTOASTtable,too,ifthetuplerequiresanyout-of-linedata.Only*HEAP_INSERT_SPECULATIVEisexplicitlyignored,asthetoastdatadoesnot*partakeinspeculativeinsertion.*注意:在插入到堆的TOAST表时,如需要out-of-line数据,那么也会应用这些选项.*只有HEAP_INSERT_SPECULATIVE选项是显式忽略的,因为toast数据不能speculativeinsertion.**TheBulkInsertStateobject(ifany;bistatecanbeNULLfordefault*behavior)isalsojustpassedthroughtoRelationGetBufferForTuple.*BulkInsertState对象(如存在,位状态可以设置为NULL)也传递给RelationGetBufferForTuple函数**ThereturnvalueistheOIDassignedtothetuple(eitherhereorbythe*caller),orInvalidOidifnoOID.Theheaderfieldsof*tupareupdated*tomatchthestoredtuple;inparticulartup->t_selfreceivestheactual*TIDwherethetuplewasstored.Butnotethatanytoastingoffields*withinthetupledataisNOTreflectedinto*tup.*返回值是分配给元组的OID(在这里或由调用方指定),*如果没有OID,则是InvalidOid。*更新*tup的头部字段以匹配存储的元组;特别是tup->t_self接收元组存储的实际TID。*但是请注意,元组数据中的字段的任何toasting都不会反映到*tup中。*//*输入:relation-数据表结构体tup-HeapTuple数据(包括头部数据等),亦即数据行cid-命令ID(顺序)options-选项bistate-BulkInsert状态输出:Oid-数据表Oid*/Oidheap_insert(Relationrelation,HeapTupletup,CommandIdcid,intoptions,BulkInsertStatebistate){TransactionIdxid=GetCurrentTransactionId();//事务idHeapTupleheaptup;//HeapTuple数据,亦即数据行Bufferbuffer;//数据缓存块Buffervmbuffer=InvalidBuffer;//vm缓冲块boolall_visible_cleared=false;//标记/**Fillintupleheaderfields,assignanOID,andtoastthetupleif*necessary.*填充元组的头部字段,分配OID,如需要处理元组的toast信息**Note:belowthispoint,heaptupisthedataweactuallyintendtostore*intotherelation;tupisthecaller'soriginaluntoasteddata.*注意:在这一点以下,heaptup是我们实际打算存储到关系中的数据;*tup是调用方的原始untoasted的数据。*///插入前准备工作,比如设置t_infomask标记等heaptup=heap_prepare_insert(relation,tup,xid,cid,options);/**Findbuffertoinsertthistupleinto.Ifthepageisallvisible,*thiswillalsopintherequisitevisibilitymappage.*查找缓冲区并将此元组插入。*如页面都是可见的,这也将固定必需的可见性映射页面。*///获取相应的buffer,详见上面的子函数解析buffer=RelationGetBufferForTuple(relation,heaptup->t_len,InvalidBuffer,options,bistate,&vmbuffer,NULL);/**We'reabouttodotheactualinsert--butcheckforconflictfirst,to*avoidpossiblyhavingtorollbackworkwe'vejustdone.*即将执行实际的插入操作--但首先要检查冲突,以避免可能的回滚.**Thisissafewithoutarecheckaslongasthereisnopossibilityof*anotherprocessscanningthepagebetweenthischeckandtheinsert*beingvisibletothescan(i.e.,anexclusivebuffercontentlockis*continuouslyheldfromthispointuntilthetupleinsertisvisible).*不重新检查也是安全的,只要在该检查和插入之间不存在其他正在执行扫描页面的进程*(页面对于扫描进程是可见的)**Foraheapinsert,weonlyneedtocheckfortable-levelSSIlocks.Our*newtuplecan'tpossiblyconflictwithexistingtuplelocks,andheap*pagelocksareonlyconsolidatedversionsoftuplelocks;theydonot*lock"gaps"asindexpagelocksdo.Sowedon'tneedtospecifya*bufferwhenmakingthecall,whichmakesforafastercheck.*对于堆插入,我们只需要检查表级别的SSI锁.*新元组不可能与现有的元组锁冲突,堆页锁只是元组锁的合并版本;*它们不像索引页锁那样锁定“间隙”。*所以我们在调用时不需要指定缓冲区,这样可以更快地进行检查。*///检查序列化是否冲突CheckForSerializableConflictIn(relation,NULL,InvalidBuffer);/*NOEREPORT(ERROR)fromheretillchangesarelogged*///开始,变量+1START_CRIT_SECTION();//插入数据(详见上一节对该函数的解析)RelationPutHeapTuple(relation,buffer,heaptup,(options&HEAP_INSERT_SPECULATIVE)!=0);//如PageisAllVisibleif(PageIsAllVisible(BufferGetPage(buffer))){//复位all_visible_cleared=true;PageClearAllVisible(BufferGetPage(buffer));visibilitymap_clear(relation,ItemPointerGetBlockNumber(&(heaptup->t_self)),vmbuffer,VISIBILITYMAP_VALID_BITS);}/**XXXShouldwesetPageSetPrunableonthispage?*XXX在页面上设置PageSetPrunable标记?**Theinsertingtransactionmayeventuallyabortthusmakingthistuple*DEADandhenceavailableforpruning.Thoughwedon'twanttooptimize*foraborts,ifnoothertupleinthispageisUPDATEd/DELETEd,the*abortedtuplewillneverbepruneduntilnextvacuumistriggered.*插入事务可能会中止,从而使这个元组"死亡/DEAD",需要进行裁剪pruning。*虽然我们不想优化事务的中止处理,但是如果本页中没有其他元组被更新/删除,*中止的元组将永远不会被删除,直到下一次触发vacuum。**IfyoudoaddPageSetPrunablehere,additinheap_xlog_inserttoo.*如果在这里增加PageSetPrunable,也需要在heap_xlog_insert中添加*///设置缓冲块为脏块MarkBufferDirty(buffer);/*XLOGstuff*///记录日志if(!(options&HEAP_INSERT_SKIP_WAL)&&RelationNeedsWAL(relation)){xl_heap_insertxlrec;xl_heap_headerxlhdr;XLogRecPtrrecptr;//uint64Pagepage=BufferGetPage(buffer);//获取相应的Pageuint8info=XLOG_HEAP_INSERT;//XLOG_HEAP_INSERT->0x00intbufflags=0;/**Ifthisisacatalog,weneedtotransmitcombocidstoproperly*decode,sologthataswell.*如果这是一个catalog,需要传输combocids进行解码,因此也需要记录到日志中.*/if(RelationIsAccessibleInLogicalDecoding(relation))log_heap_new_cid(relation,heaptup);/**Ifthisisthesingleandfirsttupleonpage,wecanreinitthe*pageinsteadofrestoringthewholething.Setflag,andhide*bufferreferencesfromXLogInsert.*如果这是页面上独立的第一个元组,我们可以重新初始化页面,而不是恢复整个页面。*设置标志,并隐藏XLogInsert的缓冲区引用。*/if(ItemPointerGetOffsetNumber(&(heaptup->t_self))==FirstOffsetNumber&&PageGetMaxOffsetNumber(page)==FirstOffsetNumber){info|=XLOG_HEAP_INIT_PAGE;bufflags|=REGBUF_WILL_INIT;}//Item在页面中的偏移xlrec.offnum=ItemPointerGetOffsetNumber(&heaptup->t_self);xlrec.flags=0;//标记if(all_visible_cleared)xlrec.flags|=XLH_INSERT_ALL_VISIBLE_CLEARED;if(options&HEAP_INSERT_SPECULATIVE)xlrec.flags|=XLH_INSERT_IS_SPECULATIVE;Assert(ItemPointerGetBlockNumber(&heaptup->t_self)==BufferGetBlockNumber(buffer));/**Forlogicaldecoding,weneedthetupleevenifwe'redoingafull*pagewrite,somakesureit'sincludedevenifwetakeafull-page*image.(XXXWecouldalternativelystoreapointerintotheFPW).*对于逻辑解码,即使正在进行全pagewrite,也需要元组数据,*以确保该元组在全页面镜像时包含在内.*(XXX我们也可以将指针存储到FPW中)*/if(RelationIsLogicallyLogged(relation)){xlrec.flags|=XLH_INSERT_CONTAINS_NEW_TUPLE;bufflags|=REGBUF_KEEP_DATA;}XLogBeginInsert();//开始WAL插入XLogRegisterData((char*)&xlrec,SizeOfHeapInsert);//注册数据//设置标记xlhdr.t_infomask2=heaptup->t_data->t_infomask2;xlhdr.t_infomask=heaptup->t_data->t_infomask;xlhdr.t_hoff=heaptup->t_data->t_hoff;/**notewemarkxlhdrasbelongingtobuffer;ifXLogInsertdecidesto*writethewholepagetothexlog,wedon'tneedtostore*xl_heap_headerinthexlog.*注意:我们标记xlhdr属于缓冲区;*如果XLogInsert确定把整个page写入到xlog中,那么不需要在xlog中存储xl_heap_header*/XLogRegisterBuffer(0,buffer,REGBUF_STANDARD|bufflags);//标记XLogRegisterBufData(0,(char*)&xlhdr,SizeOfHeapHeader);//tuple头部/*PG73FORMAT:writebitmap[+padding][+oid]+data*/XLogRegisterBufData(0,(char*)heaptup->t_data+SizeofHeapTupleHeader,heaptup->t_len-SizeofHeapTupleHeader);//tuple实际数据/*filteringbyoriginonarowlevelismuchmoreefficient*///根据行级别上的原点进行过滤要有效得多XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);//插入数据recptr=XLogInsert(RM_HEAP_ID,info);//设置LSNPageSetLSN(page,recptr);}//完成!END_CRIT_SECTION();//解锁Buffer,包括vmbufferUnlockReleaseBuffer(buffer);if(vmbuffer!=InvalidBuffer)ReleaseBuffer(vmbuffer);/**Iftupleiscachable,markitforinvalidationfromthecachesincase*weabort.NoteitisOKtodothisafterreleasingthebuffer,because*theheaptupdatastructureisallinlocalmemory,notintheshared*buffer.*如果tuple已缓存,在终止事务时,则将它标记为无效缓存。*注意,在释放缓冲区之后这样做是可以的,*因为heaptup数据结构都在本地内存中,而不是在共享缓冲区中。*///缓存操作后变“无效”的TupleCacheInvalidateHeapTuple(relation,heaptup,NULL);/*Note:speculativeinsertionsarecountedtoo,evenifabortedlater*///注意:speculativeinsertions也会统计(即使终止此事务)//更新统计信息pgstat_count_heap_insert(relation,1);/**Ifheaptupisaprivatecopy,releaseit.Don'tforgettocopyt_self*backtothecaller'simage,too.*如果heaptup是一个私有的拷贝,释放之.*不要忘了把t_self拷贝回调用者的镜像中.*/if(heaptup!=tup){tup->t_self=heaptup->t_self;heap_freetuple(heaptup);}returnHeapTupleGetOid(tup);}三、跟踪分析

测试脚本如下

--HashPartitiondroptableifexistst_wal_partition;createtablet_wal_partition(c1intnotnull,c2varchar(40),c3varchar(40))partitionbyhash(c1);createtablet_wal_partition_1partitionoft_wal_partitionforvalueswith(modulus6,remainder0);createtablet_wal_partition_2partitionoft_wal_partitionforvalueswith(modulus6,remainder1);createtablet_wal_partition_3partitionoft_wal_partitionforvalueswith(modulus6,remainder2);createtablet_wal_partition_4partitionoft_wal_partitionforvalueswith(modulus6,remainder3);createtablet_wal_partition_5partitionoft_wal_partitionforvalueswith(modulus6,remainder4);createtablet_wal_partition_6partitionoft_wal_partitionforvalueswith(modulus6,remainder5);--插入路由--deletefromt_wal_partitionwherec1=0;insertintot_wal_partition(c1,c2,c3)VALUES(0,'HASH0','HAHS0');

启动gdb,设置断点,进入heap_insert

(gdb)bheap_insertBreakpoint1at0x4df4d1:fileheapam.c,line2449.(gdb)cContinuing.Breakpoint1,heap_insert(relation=0x7f9c470a8bd8,tup=0x2908850,cid=0,options=0,bistate=0x0)atheapam.c:24492449TransactionIdxid=GetCurrentTransactionId();

构造Heap Tuple数据/获取相应的Buffer(102号)

2449TransactionIdxid=GetCurrentTransactionId();(gdb)n2452Buffervmbuffer=InvalidBuffer;(gdb)2453boolall_visible_cleared=false;(gdb)2462heaptup=heap_prepare_insert(relation,tup,xid,cid,options);(gdb)2468buffer=RelationGetBufferForTuple(relation,heaptup->t_len,(gdb)2487CheckForSerializableConflictIn(relation,NULL,InvalidBuffer);(gdb)p*heaptup$1={t_len=172,t_self={ip_blkid={bi_hi=65535,bi_lo=65535},ip_posid=0},t_tableOid=1247,t_data=0x2908868}(gdb)pbuffer$2=102

插入到Buffer中,标记Buffer为Dirty

(gdb)n2490START_CRIT_SECTION();(gdb)2493(options&HEAP_INSERT_SPECULATIVE)!=0);(gdb)2492RelationPutHeapTuple(relation,buffer,heaptup,(gdb)2495if(PageIsAllVisible(BufferGetPage(buffer)))(gdb)2515MarkBufferDirty(buffer);(gdb)2518if(!(options&HEAP_INSERT_SKIP_WAL)&&RelationNeedsWAL(relation))(gdb)

进入WAL处理部分,获取page(位于1号Page)等信息

(gdb)n2523Pagepage=BufferGetPage(buffer);(gdb)2524uint8info=XLOG_HEAP_INSERT;(gdb)2525intbufflags=0;(gdb)2531if(RelationIsAccessibleInLogicalDecoding(relation))(gdb)ppage$3=(Page)0x7f9c1a505380"\001"(gdb)p*page$4=1'\001'(gdb)pinfo$5=0'\000'

设置xl_heap_insert结构体
其中偏移=34,标记位为0x0

(gdb)n2539if(ItemPointerGetOffsetNumber(&(heaptup->t_self))==FirstOffsetNumber&&(gdb)2546xlrec.offnum=ItemPointerGetOffsetNumber(&heaptup->t_self);(gdb)2547xlrec.flags=0;(gdb)2548if(all_visible_cleared)(gdb)2550if(options&HEAP_INSERT_SPECULATIVE)(gdb)2552Assert(ItemPointerGetBlockNumber(&heaptup->t_self)==BufferGetBlockNumber(buffer));(gdb)2559if(RelationIsLogicallyLogged(relation)&&(gdb)(gdb)pxlrec$6={offnum=34,flags=0'\000'}

开始插入WAL,并注册相关数据

(gdb)2566XLogBeginInsert();(gdb)n2567XLogRegisterData((char*)&xlrec,SizeOfHeapInsert);(gdb)2569xlhdr.t_infomask2=heaptup->t_data->t_infomask2;(gdb)n2570xlhdr.t_infomask=heaptup->t_data->t_infomask;(gdb)n2571xlhdr.t_hoff=heaptup->t_data->t_hoff;(gdb)2578XLogRegisterBuffer(0,buffer,REGBUF_STANDARD|bufflags);(gdb)2579XLogRegisterBufData(0,(char*)&xlhdr,SizeOfHeapHeader);(gdb)2583heaptup->t_len-SizeofHeapTupleHeader);(gdb)2581XLogRegisterBufData(0,(gdb)2582(char*)heaptup->t_data+SizeofHeapTupleHeader,(gdb)2581XLogRegisterBufData(0,(gdb)2586XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);(gdb)2588recptr=XLogInsert(RM_HEAP_ID,info);(gdb)(gdb)pxlhdr$8={t_infomask2=30,t_infomask=2057,t_hoff=32''}

调用XLogInsert,插入WAL,并设置LSN

(gdb)n2590PageSetLSN(page,recptr);(gdb)precptr$9=5411089336(gdb)pinfo$10=0'\000'(gdb)

执行其他后续操作,完成函数调用

(gdb)n2593END_CRIT_SECTION();(gdb)2595UnlockReleaseBuffer(buffer);(gdb)2596if(vmbuffer!=InvalidBuffer)(gdb)2605CacheInvalidateHeapTuple(relation,heaptup,NULL);(gdb)2608pgstat_count_heap_insert(relation,1);(gdb)2614if(heaptup!=tup)(gdb)2620returnHeapTupleGetOid(tup);(gdb)2621}(gdb)

感谢各位的阅读,以上就是“PostgreSQL中heap_insert函数有什么作用”的内容了,经过本文的学习后,相信大家对PostgreSQL中heap_insert函数有什么作用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!