PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么
这篇文章主要介绍“PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么”,在日常操作中,相信很多人在PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
ExecutePlan函数处理查询计划,直到检索到指定数量(参数numbertuple)的元组,并沿着指定的方向扫描。ExecSeqScan函数顺序扫描relation,返回下一个符合条件的元组。
一、数据结构Plan
所有计划节点通过将Plan结构作为第一个字段从Plan结构“派生”。这确保了在将节点转换为计划节点时,一切都能正常工作。(在执行器中以通用方式传递时,节点指针经常被转换为Plan *)
/*----------------*Plannode**Allplannodes"derive"fromthePlanstructurebyhavingthe*Planstructureasthefirstfield.Thisensuresthateverythingworks*whennodesarecasttoPlan's.(nodepointersarefrequentlycasttoPlan**whenpassedaroundgenericallyintheexecutor)*所有计划节点通过将Plan结构作为第一个字段从Plan结构“派生”。*这确保了在将节点转换为计划节点时,一切都能正常工作。*(在执行器中以通用方式传递时,节点指针经常被转换为Plan*)**WeneveractuallyinstantiateanyPlannodes;thisisjustthecommon*abstractsuperclassforallPlan-typenodes.*从未实例化任何Plan节点;这只是所有Plan-type节点的通用抽象超类。*----------------*/typedefstructPlan{NodeTagtype;//节点类型/**成本估算信息;estimatedexecutioncostsforplan(seecostsize.cformoreinfo)*/Coststartup_cost;/*启动成本;costexpendedbeforefetchinganytuples*/Costtotal_cost;/*总成本;totalcost(assumingalltuplesfetched)*//**优化器估算信息;planner'sestimateofresultsizeofthisplanstep*/doubleplan_rows;/*行数;numberofrowsplanisexpectedtoemit*/intplan_width;/*平均行大小(Byte为单位);averagerowwidthinbytes*//**并行执行相关的信息;informationneededforparallelquery*/boolparallel_aware;/*是否参与并行执行逻辑?engageparallel-awarelogic?*/boolparallel_safe;/*是否并行安全;OKtouseaspartofparallelplan?*//**Plan类型节点通用的信息.CommonstructuraldataforallPlantypes.*/intplan_node_id;/*uniqueacrossentirefinalplantree*/List*targetlist;/*targetlisttobecomputedatthisnode*/List*qual;/*implicitly-ANDedqualconditions*/structPlan*lefttree;/*inputplantree(s)*/structPlan*righttree;List*initPlan;/*InitPlannodes(un-correlatedexpr*subselects)*//**Informationformanagementofparameter-change-drivenrescanning*parameter-change-driven重扫描的管理信息.**extParamincludestheparamIDsofallexternalPARAM_EXECparams*affectingthisplannodeoritschildren.setParamparamsfromthe*node'sinitPlansarenotincluded,buttheirextParamsare.**allParamincludesalltheextParamparamIDs,plustheIDsoflocal*paramsthataffectthenode(i.e.,thesetParamsofitsinitplans).*Theseare_all_thePARAM_EXECparamsthataffectthisnode.*/Bitmapset*extParam;Bitmapset*allParam;}Plan;二、源码解读
ExecutePlan
PortalRunSelect->ExecutorRun->ExecutePlan函数处理查询计划,直到检索到指定数量(参数numbertuple)的元组,并沿着指定的方向扫描.
/*----------------------------------------------------------------*ExecutePlan**Processesthequeryplanuntilwehaveretrieved'numberTuples'tuples,*movinginthespecifieddirection.*处理查询计划,直到检索到指定数量(参数numbertuple)的元组,并沿着指定的方向移动。**RunstocompletionifnumberTuplesis0*如参数numbertuple为0,则运行至结束为止**Note:thectidattributeisa'junk'attributethatisremovedbeforethe*usercanseeit*注意:ctid属性是"junk"属性,在返回给用户前会移除*----------------------------------------------------------------*/staticvoidExecutePlan(EState*estate,//执行状态PlanState*planstate,//计划状态booluse_parallel_mode,//是否使用并行模式CmdTypeoperation,//操作类型boolsendTuples,//是否需要传输元组uint64numberTuples,//元组数量ScanDirectiondirection,//扫描方向DestReceiver*dest,//接收的目标端boolexecute_once)//是否只执行一次{TupleTableSlot*slot;//元组表Slotuint64current_tuple_count;//当前的元组计数/**initializelocalvariables*初始化本地变量*/current_tuple_count=0;/**Setthedirection.*设置扫描方向*/estate->es_direction=direction;/**Iftheplanmightpotentiallybeexecutedmultipletimes,wemustforce*ittorunwithoutparallelism,becausewemightexitearly.*如果计划可能被多次执行,那么必须强制它在非并行的情况下运行,因为可能会提前退出。*/if(!execute_once)use_parallel_mode=false;//如需多次执行,则不允许并行执行estate->es_use_parallel_mode=use_parallel_mode;if(use_parallel_mode)EnterParallelMode();//如并行,则进入并行模式/**Loopuntilwe'veprocessedthepropernumberoftuplesfromtheplan.*循环直至执行计划已处理完成相应数量的元组*注意:每次循环只处理一个元组,每次都要重置元组Expr的上下文/过滤不需要的列/发送元组*/for(;;){/*Resettheper-output-tupleexprcontext*///重置Expr上下文ResetPerTupleExprContext(estate);/**Executetheplanandobtainatuple*执行计划,获取一个元组*/slot=ExecProcNode(planstate);/**ifthetupleisnull,thenweassumethereisnothingmoreto*processsowejustendtheloop...*如果返回的元组为空,那么可以认为没有什么要处理的了,结束循环……*/if(TupIsNull(slot)){/**Ifweknowwewon'tneedtobackup,wecanreleaseresources*atthispoint.*如果已知不需要备份(回溯),那么可以释放资源了*/if(!(estate->es_top_eflags&EXEC_FLAG_BACKWARD))(void)ExecShutdownNode(planstate);break;}/**Ifwehaveajunkfilter,thenprojectanewtuplewiththejunk*removed.*如有junk过滤器,使用junk执行投影操作,产生一个新的元组**Storethisnew"clean"tupleinthejunkfilter'sresultSlot.*(Formerly,westoreditbackoverthe"dirty"tuple,whichisWRONG*becausethattupleslothasthewrongdescriptor.)*将这个新的“clean”元组存储在junkfilter的resultSlot中。*(以前,将其存储在“dirty”tuple上,这是错误的,因为该tupleslot的描述符是错误的。)*/if(estate->es_junkFilter!=NULL)slot=ExecFilterJunk(estate->es_junkFilter,slot);/**Ifwearesupposedtosendthetuplesomewhere,doso.(In*practice,thisisprobablyalwaysthecaseatthispoint.)*如果要将元组发送到某个地方(接收器),那么就这样做。*(实际上,在这一点上可能总是如此。)*/if(sendTuples){/**Ifwearenotabletosendthetuple,weassumethedestination*hasclosedandnomoretuplescanbesent.Ifthat'sthecase,*endtheloop.*如果不能发送元组,有理由假设目的接收器已经关闭,不能发送更多元组,结束循环。*/if(!dest->receiveSlot(slot,dest))break;//跳出循环}/**Counttuplesprocessed,ifthisisaSELECT.(Forotheroperation*types,theModifyTableplannodemustcounttheappropriate*events.)*如果操作类型为CMD_SELECT,则计算已处理的元组。*(对于其他操作类型,ModifyTableplan节点必须统计合适的事件。)*/if(operation==CMD_SELECT)(estate->es_processed)++;/**checkourtuplecount..ifwe'veprocessedthepropernumberthen*quit,elseloopagainandprocessmoretuples.ZeronumberTuples*meansnolimit.*检查处理的元组计数…*如果已完成处理,那么退出,否则再次循环并处理更多元组。*注意:numberTuples=0表示没有限制。*/current_tuple_count++;if(numberTuples&&numberTuples==current_tuple_count){/**Ifweknowwewon'tneedtobackup,wecanreleaseresources*atthispoint.*不需要回溯,可以在此时释放资源。*/if(!(estate->es_top_eflags&EXEC_FLAG_BACKWARD))(void)ExecShutdownNode(planstate);break;}}if(use_parallel_mode)ExitParallelMode();//退出并行模式}/*----------------------------------------------------------------*ExecProcNode**Executethegivennodetoreturna(nother)tuple.*调用node->ExecProcNode函数返回元组(oneoranother)*----------------------------------------------------------------*/#ifndefFRONTENDstaticinlineTupleTableSlot*ExecProcNode(PlanState*node){if(node->chgParam!=NULL)/*参数变化?somethingchanged?*/ExecReScan(node);/*调用ExecReScan函数;letReScanhandlethis*/returnnode->ExecProcNode(node);//执行ExecProcNode}#endif
ExecSeqScan
ExecSeqScan函数顺序扫描relation,返回下一个符合条件的元组。
/*----------------------------------------------------------------*ExecSeqScan(node)**Scanstherelationsequentiallyandreturnsthenextqualifying*tuple.*WecalltheExecScan()routineandpassittheappropriate*accessmethodfunctions.*顺序扫描relation,返回下一个符合条件的元组。*调用ExecScan函数,传入相应的访问方法函数*----------------------------------------------------------------*/staticTupleTableSlot*ExecSeqScan(PlanState*pstate){SeqScanState*node=castNode(SeqScanState,pstate);//获取SeqScanStatereturnExecScan(&node->ss,(ExecScanAccessMtd)SeqNext,(ExecScanRecheckMtd)SeqRecheck);//执行Scan}/*----------------------------------------------------------------*ExecScan**Scanstherelationusingthe'accessmethod'indicatedand*returnsthenextqualifyingtupleinthedirectionspecified*intheglobalvariableExecDirection.*TheaccessmethodreturnsthenexttupleandExecScan()is*responsibleforcheckingthetuplereturnedagainstthequal-clause.*使用指定的“访问方法”扫描关系,并按照全局变量ExecDirection中指定的方向返回下一个符合条件的元组。*访问方法返回下一个元组,ExecScan()负责根据qual-clause条件子句检查返回的元组是否符合条件。**A'recheckmethod'mustalsobeprovidedthatcancheckan*arbitrarytupleoftherelationagainstanyqualconditions*thatareimplementedinternaltotheaccessmethod.*调用者还必须提供“recheckmethod”,根据访问方法内部实现的条件检查关系的所有元组。**Conditions:*--the"cursor"maintainedbytheAMIispositionedatthetuple*returnedpreviously.*前提条件:*由AMI负责维护的游标已由先前的处理过程定位.**InitialStates:*--therelationindicatedisopenedforscanningsothatthe*"cursor"ispositionedbeforethefirstqualifyingtuple.*初始状态:*在游标可定位返回第一个符合条件的元组前,relation已打开可进行扫描*----------------------------------------------------------------*/TupleTableSlot*ExecScan(ScanState*node,ExecScanAccessMtdaccessMtd,/*返回元组的访问方法;functionreturningatuple*/ExecScanRecheckMtdrecheckMtd)//recheck方法{ExprContext*econtext;//表达式上下文ExprState*qual;//表达式状态ProjectionInfo*projInfo;//投影信息/**Fetchdatafromnode*从node中提取数据*/qual=node->ps.qual;projInfo=node->ps.ps_ProjInfo;econtext=node->ps.ps_ExprContext;/*interruptchecksareinExecScanFetch*///在ExecScanFetch中有中断检查/**Ifwehaveneitheraqualtochecknoraprojectiontodo,justskip*alltheoverheadandreturntherawscantuple.*如果既没有要检查的条件qual,也没有要做的投影操作,那么就跳过所有的操作并返回rawscan元组。*/if(!qual&&!projInfo){ResetExprContext(econtext);returnExecScanFetch(node,accessMtd,recheckMtd);}/**Resetper-tuplememorycontexttofreeanyexpressionevaluation*storageallocatedintheprevioustuplecycle.*重置每个元组内存上下文,以释放用于在前一个元组循环中分配的表达式求值内存空间。*/ResetExprContext(econtext);/**getatuplefromtheaccessmethod.Loopuntilweobtainatuplethat*passesthequalification.*从访问方法中获取一个元组。循环,直到获得通过限定条件的元组。*/for(;;){TupleTableSlot*slot;//slot变量slot=ExecScanFetch(node,accessMtd,recheckMtd);//获取slot/**iftheslotreturnedbytheaccessMtdcontainsNULL,thenitmeans*thereisnothingmoretoscansowejustreturnanemptyslot,*beingcarefultousetheprojectionresultslotsoithascorrect*tupleDesc.*如果accessMtd方法返回的slot中包含NULL,那么这意味着不再需要扫描了,*这时候只需要返回一个空slot,小心使用投影结果slot,这样可以有正确的tupleDesc了。*/if(TupIsNull(slot)){if(projInfo)returnExecClearTuple(projInfo->pi_state.resultslot);elsereturnslot;}/**placethecurrenttupleintotheexprcontext*把当前tuple放入到expr上下文中*/econtext->ecxt_scantuple=slot;/**checkthatthecurrenttuplesatisfiesthequal-clause*检查当前的tuple是否符合qual-clause条件**checkfornon-nullqualheretoavoidafunctioncalltoExecQual()*whenthequalisnull...savesonlyafewcycles,buttheyaddup*...*在这里检查qual是否非空,以避免在qual为空时调用ExecQual()函数…*只节省了几个调用周期,但它们加起来……的成本还是蛮可观的*/if(qual==NULL||ExecQual(qual,econtext)){/**Foundasatisfactoryscantuple.*发现一个满足条件的元组*/if(projInfo){/**Formaprojectiontuple,storeitintheresulttupleslot*andreturnit.*构造一个投影元组,存储在结果元组slot中并返回*/returnExecProject(projInfo);//执行投影操作并返回}else{/**Here,wearen'tprojecting,sojustreturnscantuple.*不需要执行投影操作,返回元组*/returnslot;//直接返回}}elseInstrCountFiltered1(node,1);//instrument计数/**Tuplefailsqual,sofreeper-tuplememoryandtryagain.*元组不满足条件,释放资源,重试*/ResetExprContext(econtext);}}/**ExecScanFetch--checkinterrupts&fetchnextpotentialtuple*ExecScanFetch--检查中断&提前下一个备选元组**Thisroutineisconcernedwithsubstitutingatesttupleifweare*insideanEvalPlanQualrecheck.Ifwearen't,justexecute*theaccessmethod'snext-tupleroutine.*这个例程是处理测试元组的替换(如果在EvalPlanQual重新检查中)。*如果不是在EvalPlanQual中,则执行access方法的next-tuple例程。*/staticinlineTupleTableSlot*ExecScanFetch(ScanState*node,ExecScanAccessMtdaccessMtd,ExecScanRecheckMtdrecheckMtd){EState*estate=node->ps.state;CHECK_FOR_INTERRUPTS();//检查中断if(estate->es_epqTuple!=NULL)//如es_epqTuple不为NULL(){//es_epqTuple字段用于在READCOMMITTED模式中替换更新后的元组后,重新评估是否满足执行计划的条件quals/**WeareinsideanEvalPlanQualrecheck.Returnthetesttupleif*oneisavailable,afterrecheckinganyaccess-method-specific*conditions.*我们正在EvalPlanQual复查。*如果testtuple可用,则在重新检查所有特定于访问方法的条件后返回该元组。*/Indexscanrelid=((Scan*)node->ps.plan)->scanrelid;//访问的relidif(scanrelid==0)//relid==0{TupleTableSlot*slot=node->ss_ScanTupleSlot;/**ThisisaForeignScanorCustomScanwhichhaspusheddowna*jointotheremoteside.Therecheckmethodisresponsiblenot*onlyforrecheckingthescan/joinqualsbutalsoforstoring*thecorrecttupleintheslot.*这是一个ForeignScan或CustomScan,它将下推到远程端。*recheck方法不仅负责重新检查扫描/连接quals,还负责在slot中存储正确的元组。*/if(!(*recheckMtd)(node,slot))ExecClearTuple(slot);/*验证不通过,释放资源,不返回元组;wouldnotbereturnedbyscan*/returnslot;}elseif(estate->es_epqTupleSet[scanrelid-1])//从estate->es_epqTupleSet数组中获取标志{TupleTableSlot*slot=node->ss_ScanTupleSlot;//获取slot/*Returnemptyslotifwealreadyreturnedatuple*///如已返回元组,则清空slotif(estate->es_epqScanDone[scanrelid-1])returnExecClearTuple(slot);/*Elsemarktorememberthatweshouldn'treturnmore*///否则,标记没有返回estate->es_epqScanDone[scanrelid-1]=true;/*Returnemptyslotifwehaven'tgotatesttuple*///如testtuple为NULL,则清空slotif(estate->es_epqTuple[scanrelid-1]==NULL)returnExecClearTuple(slot);/*Storetesttupleintheplannode'sscanslot*///在计划节点的scanslot中存储testtupleExecStoreHeapTuple(estate->es_epqTuple[scanrelid-1],slot,false);/*Checkifitmeetstheaccess-methodconditions*///检查是否满足访问方法条件if(!(*recheckMtd)(node,slot))ExecClearTuple(slot);/*不满足,清空slot;wouldnotbereturnedbyscan*/returnslot;}}/**Runthenode-type-specificaccessmethodfunctiontogetthenexttuple*运行node-type-specific方法函数,获取下一个tuple*/return(*accessMtd)(node);}/**ExecProject**Projectsatuplebasedonprojectioninfoandstoresitintheslotpassed*toExecBuildProjectInfo().*根据投影信息投影一个元组,并将其存储在传递给ExecBuildProjectInfo()的slot中。**Note:theresultisalwaysavirtualtuple;thereforeitmayreference*thecontentsoftheexprContext'sscantuplesand/ortemporaryresults*constructedintheexprContext.Ifthecallerwishestheresulttobe*validlongerthanthatdatawillbevalid,hemustcallExecMaterializeSlot*ontheresultslot.*注意:结果总是一个虚拟元组;*因此,它可以引用exprContext的扫描元组和/或exprContext中构造的临时结果的内容。*如果调用者希望结果有效的时间长于数据有效的时间,必须在结果slot上调用ExecMaterializeSlot。*/#ifndefFRONTENDstaticinlineTupleTableSlot*ExecProject(ProjectionInfo*projInfo){ExprContext*econtext=projInfo->pi_exprContext;ExprState*state=&projInfo->pi_state;TupleTableSlot*slot=state->resultslot;boolisnull;/**Clearanyformercontentsoftheresultslot.Thismakesitsafefor*ustousetheslot'sDatum/isnullarraysasworkspace.*清除以前的结果slot内容。*这使得我们可以安全地使用slot的Datum/isnull数组作为工作区。*/ExecClearTuple(slot);/*Runtheexpression,discardingscalarresultfromthelastcolumn.*///运行表达式,从最后一列丢弃scalar结果。(void)ExecEvalExprSwitchContext(state,econtext,&isnull);/**Successfullyformedaresultrow.Marktheresultslotascontaininga*validvirtualtuple(inlinedversionofExecStoreVirtualTuple()).*成功形成了一个结果行。*将结果slot标记为包含一个有效的虚拟元组(ExecStoreVirtualTuple()的内联版本)。*/slot->tts_flags&=~TTS_FLAG_EMPTY;slot->tts_nvalid=slot->tts_tupleDescriptor->natts;returnslot;}#endif/**ExecQual-evaluateaqualpreparedwithExecInitQual(possiblyvia*ExecPrepareQual).Returnstrueifqualissatisfied,elsefalse.*解析用ExecInitQual准备的条件qual(可能通过ExecPrepareQual)。*如果满足条件qual,返回true,否则为false。**Note:ExecQualusedtohaveathirdargument"resultForNull".The*behaviorofthisfunctionnowcorrespondstoresultForNull==false.*IfyouwanttheresultForNull==truebehavior,seeExecCheck.*注意:ExecQual曾经有第三个参数“resultForNull”。*这个函数的行为现在对应于resultForNull==false。*如果希望resultForNull==true行为,请参阅ExecCheck。*/#ifndefFRONTENDstaticinlineboolExecQual(ExprState*state,ExprContext*econtext){Datumret;boolisnull;/*short-circuit(hereandinExecInitQual)foremptyrestrictionlist*///如state为NULL,直接返回if(state==NULL)returntrue;/*verifythatexpressionwascompiledusingExecInitQual*///使用函数ExecInitQual验证表达式是否可以编译Assert(state->flags&EEO_FLAG_IS_QUAL);ret=ExecEvalExprSwitchContext(state,econtext,&isnull);/*EEOP_QUAL不应返回NULL;EEOP_QUALshouldneverreturnNULL*/Assert(!isnull);returnDatumGetBool(ret);}#endif/*--------------------------------*ExecClearTuple**Thisfunctionisusedtoclearoutaslotinthetupletable.*该函数清空tupletable中的slot*NB:onlythetupleiscleared,notthetupledescriptor(ifany).*注意:只有tuple被清除,而不是tuple描述符*--------------------------------*/TupleTableSlot*/*返回验证通过的slot;return:slotpassed*/ExecClearTuple(TupleTableSlot*slot)/*存储tuple的slot;slotinwhichtostoretuple*/{/**sanitychecks*安全检查*/Assert(slot!=NULL);/**Freetheoldphysicaltupleifnecessary.*如需要,释放原有的物理元组*/if(TTS_SHOULDFREE(slot)){heap_freetuple(slot->tts_tuple);//释放元组slot->tts_flags&=~TTS_FLAG_SHOULDFREE;}if(TTS_SHOULDFREEMIN(slot)){heap_free_minimal_tuple(slot->tts_mintuple);slot->tts_flags&=~TTS_FLAG_SHOULDFREEMIN;}slot->tts_tuple=NULL;//设置NULL值slot->tts_mintuple=NULL;/**Dropthepinonthereferencedbuffer,ifthereisone.*如果有的话,将pin放在已引用的缓冲区上。*/if(BufferIsValid(slot->tts_buffer))ReleaseBuffer(slot->tts_buffer);//释放缓冲区slot->tts_buffer=InvalidBuffer;/**Markitempty.*标记为空*/slot->tts_flags|=TTS_FLAG_EMPTY;slot->tts_nvalid=0;returnslot;}三、跟踪分析
测试脚本如下
testdb=#explainselectdw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.jetestdb-#fromt_dwxxdw,lateral(selectgr.grbh,gr.xm,jf.ny,jf.jetestdb(#fromt_grxxgrinnerjoint_jfxxjftestdb(#ongr.dwbh=dw.dwbhtestdb(#andgr.grbh=jf.grbh)grjftestdb-#orderbydw.dwbh;QUERYPLAN------------------------------------------------------------------------------------------Sort(cost=20070.93..20320.93rows=100000width=47)SortKey:dw.dwbh->HashJoin(cost=3754.00..8689.61rows=100000width=47)HashCond:((gr.dwbh)::text=(dw.dwbh)::text)->HashJoin(cost=3465.00..8138.00rows=100000width=31)HashCond:((jf.grbh)::text=(gr.grbh)::text)->SeqScanont_jfxxjf(cost=0.00..1637.00rows=100000width=20)->Hash(cost=1726.00..1726.00rows=100000width=16)->SeqScanont_grxxgr(cost=0.00..1726.00rows=100000width=16)->Hash(cost=164.00..164.00rows=10000width=20)->SeqScanont_dwxxdw(cost=0.00..164.00rows=10000width=20)(11rows)
启动gdb,设置断点,进入ExecutePlan
(gdb)bExecutePlanBreakpoint1at0x6db79d:fileexecMain.c,line1694.(gdb)cContinuing.Breakpoint1,ExecutePlan(estate=0x14daf48,planstate=0x14db160,use_parallel_mode=false,operation=CMD_SELECT,sendTuples=true,numberTuples=0,direction=ForwardScanDirection,dest=0x14d9ed0,execute_once=true)atexecMain.c:1694warning:Sourcefileismorerecentthanexecutable.1694current_tuple_count=0;
查看输入参数
planstate->type:T_SortState->排序Plan
planstate->ExecProcNode:ExecProcNodeFirst,封装器
planstate->ExecProcNodeReal:ExecSort,实际的函数
use_parallel_mode:false,非并行模式
operation:CMD_SELECT,查询操作
sendTuples:T,需要发送元组给客户端
numberTuples:0,所有元组
direction:ForwardScanDirection
dest:printtup(console客户端)
execute_once:T,只执行一次
(gdb)p*estate$1={type=T_EState,es_direction=ForwardScanDirection,es_snapshot=0x1493e10,es_crosscheck_snapshot=0x0,es_range_table=0x14d7c00,es_plannedstmt=0x14d9d58,es_sourceText=0x13eeeb8"selectdw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je\nfromt_dwxxdw,lateral(selectgr.grbh,gr.xm,jf.ny,jf.je\n",''<repeats24times>,"fromt_grxxgrinnerjoint_jfxxjf\n",''<repeats34times>...,es_junkFilter=0x0,es_output_cid=0,es_result_relations=0x0,es_num_result_relations=0,es_result_relation_info=0x0,es_root_result_relations=0x0,es_num_root_result_relations=0,es_tuple_routing_result_relations=0x0,es_trig_target_relations=0x0,es_trig_tuple_slot=0x0,es_trig_oldtup_slot=0x0,es_trig_newtup_slot=0x0,es_param_list_info=0x0,es_param_exec_vals=0x0,es_queryEnv=0x0,es_query_cxt=0x14dae30,es_tupleTable=0x14dbaf8,es_rowMarks=0x0,es_processed=0,es_lastoid=0,es_top_eflags=16,es_instrument=0,es_finished=false,es_exprcontexts=0x14db550,es_subplanstates=0x0,es_auxmodifytables=0x0,es_per_tuple_exprcontext=0x0,es_epqTuple=0x0,es_epqTupleSet=0x0,es_epqScanDone=0x0,es_use_parallel_mode=false,es_query_dsa=0x0,es_jit_flags=0,es_jit=0x0,es_jit_worker_instr=0x0}(gdb)p*planstate$2={type=T_SortState,plan=0x14d3f90,state=0x14daf48,ExecProcNode=0x6e41bb<ExecProcNodeFirst>,ExecProcNodeReal=0x716144<ExecSort>,instrument=0x0,worker_instrument=0x0,worker_jit_instrument=0x0,qual=0x0,lefttree=0x14db278,righttree=0x0,initPlan=0x0,subPlan=0x0,chgParam=0x0,ps_ResultTupleSlot=0x14ec470,ps_ExprContext=0x0,ps_ProjInfo=0x0,scandesc=0x14e9fd0}(gdb)p*dest$4={receiveSlot=0x48cc00<printtup>,rStartup=0x48c5c1<printtup_startup>,rShutdown=0x48d02e<printtup_shutdown>,rDestroy=0x48d0a7<printtup_destroy>,mydest=DestRemote}
赋值,准备执行ExecProcNode(ExecSort)
(gdb)n1699estate->es_direction=direction;(gdb)1705if(!execute_once)(gdb)1708estate->es_use_parallel_mode=use_parallel_mode;(gdb)1709if(use_parallel_mode)(gdb)1718ResetPerTupleExprContext(estate);(gdb)1723slot=ExecProcNode(planstate);(gdb)
执行ExecProcNode(ExecSort),返回slot
(gdb)1729if(TupIsNull(slot))(gdb)p*slot$5={type=T_TupleTableSlot,tts_isempty=false,tts_shouldFree=false,tts_shouldFreeMin=false,tts_slow=false,tts_tuple=0x14ec4b0,tts_tupleDescriptor=0x14ec058,tts_mcxt=0x14dae30,tts_buffer=0,tts_nvalid=0,tts_values=0x14ec4d0,tts_isnull=0x14ec508,tts_mintuple=0x1a4b078,tts_minhdr={t_len=64,t_self={ip_blkid={bi_hi=0,bi_lo=0},ip_posid=0},t_tableOid=0,t_data=0x1a4b070},tts_off=0,tts_fixedTupleDescriptor=true}
查看slot中的数据
注意:slot中的t_data不是实际的tuple data,而是缓冲区信息,在返回时根据这些信息从缓冲区获取数据返回
(gdb)p*slot$5={type=T_TupleTableSlot,tts_isempty=false,tts_shouldFree=false,tts_shouldFreeMin=false,tts_slow=false,tts_tuple=0x14ec4b0,tts_tupleDescriptor=0x14ec058,tts_mcxt=0x14dae30,tts_buffer=0,tts_nvalid=0,tts_values=0x14ec4d0,tts_isnull=0x14ec508,tts_mintuple=0x1a4b078,tts_minhdr={t_len=64,t_self={ip_blkid={bi_hi=0,bi_lo=0},ip_posid=0},t_tableOid=0,t_data=0x1a4b070},tts_off=0,tts_fixedTupleDescriptor=true}(gdb)p*slot->tts_tuple$6={t_len=64,t_self={ip_blkid={bi_hi=0,bi_lo=0},ip_posid=0},t_tableOid=0,t_data=0x1a4b070}(gdb)p*slot->tts_tuple->t_data$7={t_choice={t_heap={t_xmin=21967600,t_xmax=0,t_field3={t_cid=56,t_xvac=56}},t_datum={datum_len_=21967600,datum_typmod=0,datum_typeid=56}},t_ctid={ip_blkid={bi_hi=0,bi_lo=0},ip_posid=32639},t_infomask2=7,t_infomask=2,t_hoff=24'\030',t_bits=0x1a4b087""}
判断是否需要过滤属性(不需要)
(gdb)n1748if(estate->es_junkFilter!=NULL)(gdb)(gdb)pestate->es_junkFilter$12=(JunkFilter*)0x0
修改计数器等信息
(gdb)1755if(sendTuples)(gdb)1762if(!dest->receiveSlot(slot,dest))(gdb)1771if(operation==CMD_SELECT)(gdb)1772(estate->es_processed)++;(gdb)pestate->es_processed$9=0(gdb)n1779current_tuple_count++;(gdb)pcurrent_tuple_count$10=0(gdb)n1780if(numberTuples&&numberTuples==current_tuple_count)(gdb)pnumberTuples$11=0(gdb)n1790}
继续循环,直接满足条件(全部扫描完毕)未知
(gdb)n1718ResetPerTupleExprContext(estate);(gdb)1723slot=ExecProcNode(planstate);(gdb)1729if(TupIsNull(slot))...
ExecutePlan的主体逻辑已介绍完毕,下面简单跟踪分析ExecSeqScan函数
设置断点,进入ExecSeqScan
(gdb)del1(gdb)cContinuing.Breakpoint2,ExecSeqScan(pstate=0x14e99a0)atnodeSeqscan.c:127warning:Sourcefileismorerecentthanexecutable.127SeqScanState*node=castNode(SeqScanState,pstate);
查看输入参数
plan为SeqScan
ExecProcNode=ExecProcNodeReal,均为函数ExecSeqScan
targetlist为投影列信息
(gdb)p*pstate$13={type=T_SeqScanState,plan=0x14d5570,state=0x14daf48,ExecProcNode=0x714d59<ExecSeqScan>,ExecProcNodeReal=0x714d59<ExecSeqScan>,instrument=0x0,worker_instrument=0x0,worker_jit_instrument=0x0,qual=0x0,lefttree=0x0,righttree=0x0,initPlan=0x0,subPlan=0x0,chgParam=0x0,ps_ResultTupleSlot=0x14e9c38,ps_ExprContext=0x14e9ab8,ps_ProjInfo=0x0,scandesc=0x7fa45b442ab8}(gdb)p*pstate->plan$14={type=T_SeqScan,startup_cost=0,total_cost=164,plan_rows=10000,plan_width=20,parallel_aware=false,parallel_safe=true,plan_node_id=7,targetlist=0x14d5438,qual=0x0,lefttree=0x0,righttree=0x0,initPlan=0x0,extParam=0x0,allParam=0x0}
进入ExecScan函数
accessMtd方法为SeqNext
recheckMtd方法为SeqRecheck
(gdb)n129returnExecScan(&node->ss,(gdb)stepExecScan(node=0x14e99a0,accessMtd=0x714c6d<SeqNext>,recheckMtd=0x714d3d<SeqRecheck>)atexecScan.c:132warning:Sourcefileismorerecentthanexecutable.132qual=node->ps.qual;
ExecScan->投影信息,为NULL
(gdb)p*projInfoCannotaccessmemoryataddress0x0
ExecScan->约束条件为NULL
(gdb)p*qualCannotaccessmemoryataddress0x0
ExecScan->如果既没有要检查的条件qual,也没有要做的投影操作,那么就跳过所有的操作并返回raw scan元组
(gdb)n142if(!qual&&!projInfo)(gdb)144ResetExprContext(econtext);(gdb)n145returnExecScanFetch(node,accessMtd,recheckMtd);
ExecScan->进入ExecScanFetch
(gdb)stepExecScanFetch(node=0x14e99a0,accessMtd=0x714c6d<SeqNext>,recheckMtd=0x714d3d<SeqRecheck>)atexecScan.c:3939EState*estate=node->ps.state;
ExecScan->检查中断,判断是否处于EvalPlanQual recheck状态(为NULL,实际不是)
39EState*estate=node->ps.state;(gdb)n41CHECK_FOR_INTERRUPTS();(gdb)43if(estate->es_epqTuple!=NULL)(gdb)p*estate->es_epqTupleCannotaccessmemoryataddress0x0
ExecScan->调用访问方法SeqNext,返回slot
(gdb)n95return(*accessMtd)(node);(gdb)n96}
ExecScan->回到ExecScan&ExecSeqScan,结束调用
(gdb)nExecScan(node=0x14e99a0,accessMtd=0x714c6d<SeqNext>,recheckMtd=0x714d3d<SeqRecheck>)atexecScan.c:219219}(gdb)ExecSeqScan(pstate=0x14e99a0)atnodeSeqscan.c:132132}(gdb)
到此,关于“PostgreSQL中ExecutePlan函数与ExecSeqScan函数的作用是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。