本篇内容介绍了“PostgreSQL中PortalRun->PortalRunSelect函数的实现逻辑是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

一、数据结构

Portal
对于Portals(客户端请求),有几种执行策略,具体取决于要执行什么查询。
(注意:无论什么情况下,一个Portal只执行一个source-SQL查询,因此从用户的角度来看只产生一个结果。

/**WehaveseveralexecutionstrategiesforPortals,dependingonwhat*queryorqueriesaretobeexecuted.(Note:inallcases,aPortal*executesjustasinglesource-SQLquery,andthusproducesjusta*singleresultfromtheuser'sviewpoint.However,therulerewriter*mayexpandthesinglesourcequerytozeroormanyactualqueries.)*对于Portals(客户端请求),有几种执行策略,具体取决于要执行什么查询。*(注意:无论什么情况下,一个Portal只执行一个source-SQL查询,因此从用户的角度来看只产生一个结果。*但是,规则重写器可以将单个源查询扩展为零或多个实际查询。**PORTAL_ONE_SELECT:theportalcontainsonesingleSELECTquery.Werun*theExecutorincrementallyasresultsaredemanded.Thisstrategyalso*supportsholdablecursors(theExecutorresultscanbedumpedintoa*tuplestoreforaccessaftertransactioncompletion).*PORTAL_ONE_SELECT:包含一个SELECT查询。*按需要的结果重复(递增)地运行执行器。*该策略还支持可持有游标(执行器结果可以在事务完成后转储到tuplestore中进行访问)。**PORTAL_ONE_RETURNING:theportalcontainsasingleINSERT/UPDATE/DELETE*querywithaRETURNINGclause(pluspossiblyauxiliaryqueriesaddedby*rulerewriting).Onfirstexecution,weruntheportaltocompletion*anddumptheprimaryquery'sresultsintotheportaltuplestore;the*resultsarethenreturnedtotheclientasdemanded.(Wecan'tsupport*suspensionofthequerypartwaythrough,becausetheAFTERTRIGGERcode*can'tcope,andalsobecausewedon'twanttoriskfailingtoexecute*alltheauxiliaryqueries.)*PORTAL_ONE_RETURNING:包含一个带有RETURNING子句的INSERT/UPDATE/DELETE查询(可能还包括由规则重写添加的辅助查询)。*在第一次执行时,运行Portal来完成并将主查询的结果转储到Portal的tuplestore中;*然后根据需要将结果返回给客户端。*(我们不能支持半途中断的查询,因为AFTER触发器代码无法处理,*也因为不想冒执行所有辅助查询失败的风险)。**PORTAL_ONE_MOD_WITH:theportalcontainsonesingleSELECTquery,but*ithasdata-modifyingCTEs.Thisiscurrentlytreatedthesameasthe*PORTAL_ONE_RETURNINGcasebecauseofthepossibilityofneedingtofire*triggers.ItmayactmorelikePORTAL_ONE_SELECTinfuture.*PORTAL_ONE_MOD_WITH:只包含一个SELECT查询,但它具有数据修改的CTEs。*这与PORTAL_ONE_RETURNING的情况相同,因为可能需要触发触发器。将来它的行为可能更像PORTAL_ONE_SELECT。**PORTAL_UTIL_SELECT:theportalcontainsautilitystatementthatreturns*aSELECT-likeresult(forexample,EXPLAINorSHOW).Onfirstexecution,*werunthestatementanddumpitsresultsintotheportaltuplestore;*theresultsarethenreturnedtotheclientasdemanded.*PORTAL_UTIL_SELECT:包含一个实用程序语句,该语句返回一个类似SELECT的结果(例如,EXPLAIN或SHOW)。*在第一次执行时,运行语句并将其结果转储到portaltuplestore;然后根据需要将结果返回给客户端。**PORTAL_MULTI_QUERY:allothercases.Here,wedonotsupportpartial*execution:theportal'squerieswillberuntocompletiononfirstcall.*PORTAL_MULTI_QUERY:除上述情况外的其他情况。*在这里,不支持部分执行:Portal的查询语句将在第一次调用时运行到完成。*/typedefenumPortalStrategy{PORTAL_ONE_SELECT,PORTAL_ONE_RETURNING,PORTAL_ONE_MOD_WITH,PORTAL_UTIL_SELECT,PORTAL_MULTI_QUERY}PortalStrategy;/**Aportalisalwaysinoneofthesestates.Itispossibletotransit*fromACTIVEbacktoREADYifthequeryisnotruntocompletion;*otherwiseweneverbackupinstatus.*Portal总是处于这些状态中的之一。*如果查询没有运行到完成,则可以从活动状态转回准备状态;否则永远不会后退。*/typedefenumPortalStatus{PORTAL_NEW,/*刚创建;freshlycreated*/PORTAL_DEFINED,/*PortalDefineQuery完成;PortalDefineQuerydone*/PORTAL_READY,/*PortalStart完成;PortalStartcomplete,canrunit*/PORTAL_ACTIVE,/*Portal正在运行;portalisrunning(can'tdeleteit)*/PORTAL_DONE,/*Portal已经完成;portalisfinished(don'tre-runit)*/PORTAL_FAILED/*Portal出现错误;portalgoterror(can'tre-runit)*/}PortalStatus;typedefstructPortalData*Portal;//结构体指针typedefstructPortalData{/*Bookkeepingdata*/constchar*name;/*portal的名称;portal'sname*/constchar*prepStmtName;/*已完成准备的源语句;sourcepreparedstatement(NULLifnone)*/MemoryContextportalContext;/*内存上下文;subsidiarymemoryforportal*/ResourceOwnerresowner;/*资源的owner;resourcesownedbyportal*/void(*cleanup)(Portalportal);/*cleanup钩子函数;cleanuphook*//**Statedataforrememberingwhichsubtransaction(s)theportalwas*createdorusedin.Iftheportalisheldoverfromaprevious*transaction,bothsubxidsareInvalidSubTransactionId.Otherwise,*createSubidisthecreatingsubxactandactiveSubidisthelastsubxact*inwhichwerantheportal.*状态数据,用于记住在哪个子事务中创建或使用Portal。*如果Portal是从以前的事务中持有的,那么两个subxids都应该是InvalidSubTransactionId。*否则,createSubid是正在创建的subxact,而activeSubid是运行Portal的最后一个subxact。*/SubTransactionIdcreateSubid;/*正在创建的subxact;thecreatingsubxact*/SubTransactionIdactiveSubid;/*活动的最后一个subxact;thelastsubxactwithactivity*//*Thequeryorqueriestheportalwillexecute*///portal将会执行的查询constchar*sourceText;/*查询的源文本;textofquery(asof8.4,neverNULL)*/constchar*commandTag;/*源查询的命令tag;commandtagfororiginalquery*/List*stmts;/*PlannedStmt链表;listofPlannedStmts*/CachedPlan*cplan;/*缓存的PlannedStmts;CachedPlan,ifstmtsarefromone*/ParamListInfoportalParams;/*传递给查询的参数;paramstopasstoquery*/QueryEnvironment*queryEnv;/*查询的执行环境;environmentforquery*//*Features/options*/PortalStrategystrategy;/*场景;seeabove*/intcursorOptions;/*DECLARECURSOR选项位;DECLARECURSORoptionbits*/boolrun_once;/*是否只执行一次;portalwillonlyberunonce*//*Statusdata*/PortalStatusstatus;/*Portal的状态;seeabove*/boolportalPinned;/*是否不能被清除;apinnedportalcan'tbedropped*/boolautoHeld;/*是否自动从pinned到held;wasautomaticallyconvertedfrompinnedto*held(seeHoldPinnedPortals())*//*IfnotNULL,Executorisactive;callExecutorEndeventually:*///如不为NULL,执行器处于活动状态QueryDesc*queryDesc;/*执行器需要使用的信息;infoneededforexecutorinvocation*//*Ifportalreturnstuples,thisistheirtupdesc:*///如Portal需要返回元组,这是元组的描述TupleDesctupDesc;/*结果元组的描述;descriptorforresulttuples*//*andthesearetheformatcodestouseforthecolumns:*///列信息的格式码int16*formats;/*每一列的格式码;aformatcodeforeachcolumn*//**WherewestoretuplesforaheldcursororaPORTAL_ONE_RETURNINGor*PORTAL_UTIL_SELECTquery.(Acursorheldpasttheendofits*transactionnolongerhasanyactiveexecutorstate.)*在这里,为持有的游标或PORTAL_ONE_RETURNING或PORTAL_UTIL_SELECT存储元组。*(在事务结束后持有的游标不再具有任何活动执行器状态。)*/Tuplestorestate*holdStore;/*存储持有的游标信息;storeforholdablecursors*/MemoryContextholdContext;/*持有holdStore的内存上下文;memorycontainingholdStore*//**SnapshotunderwhichtuplesintheholdStorewereread.Wemustkeepa*referencetothissnapshotifthereisanypossibilitythatthetuples*containTOASTreferences,becausereleasingthesnapshotcouldallow*recently-deadrowstobevacuumedaway,alongwithanytoastdata*belongingtothem.Inthecaseofaheldcursor,weavoidneedingto*keepsuchasnapshotbyforciblydetoastingthedata.*读取holdStore中元组的Snapshot。*如果元组包含TOAST引用的可能性存在,那么必须保持对该快照的引用,*因为释放快照可能会使最近废弃的行与属于它们的TOAST数据一起被清除。*对于持有的游标,通过强制解压数据来避免需要保留这样的快照。*/SnapshotholdSnapshot;/*已注册的快照信息,如无则为NULL;registeredsnapshot,orNULLifnone*//**atStart,atEndandportalPosindicatethecurrentcursorposition.*portalPosiszerobeforethefirstrow,NafterfetchingN'throwof*query.Afterwerunofftheend,portalPos=#ofrowsinquery,and*atEndistrue.NotethatatStartimpliesportalPos==0,butnotthe*reverse:wemighthavebackeduponlyasfarasthefirstrow,notto*thestart.AlsonotethatvariouscodeinspectsatStartandatEnd,but*onlytheportalmovementroutinesshouldtouchportalPos.*atStart、atEnd和portalPos表示当前光标的位置。*portalPos在第一行之前为0,在获取第N行查询后为N。*在运行结束后,portalPos=#查询中的行号,atEnd为T。*注意,atStart表示portalPos==0,但不是相反:我们可能只回到到第一行,而不是开始。*还要注意,各种代码在开始和结束时都要检查,但是只有Portal移动例程应该访问portalPos。*/boolatStart;//处于开始位置?boolatEnd;//处于结束位置?uint64portalPos;//实际行号/*Presentationdata,primarilyusedbythepg_cursorssystemview*///用于表示的数据,主要由pg_cursors系统视图使用TimestampTzcreation_time;/*portal定义的时间;timeatwhichthisportalwasdefined*/boolvisible;/*是否在pg_cursors中可见?includethisportalinpg_cursors?*/}PortalData;/**PortalIsValid*Trueiffportalisvalid.*判断Portal是否有效*/#definePortalIsValid(p)PointerIsValid(p)

QueryDesc
QueryDesc封装了执行器执行查询所需的所有内容。

/*----------------*querydescriptor:**aQueryDescencapsulateseverythingthattheexecutor*needstoexecutethequery.*QueryDesc封装了执行器执行查询所需的所有内容。**FortheconvenienceofSQL-languagefunctions,wealsosupportQueryDescs*containingutilitystatements;thesemustnotbepassedtotheexecutor*however.*为了使用SQL函数,还需要支持包含实用语句的QueryDescs;*但是,这些内容不能传递给执行程序。*---------------------*/typedefstructQueryDesc{/*ThesefieldsareprovidedbyCreateQueryDesc*///以下变量由CreateQueryDesc函数设置CmdTypeoperation;/*操作类型,如CMD_SELECT等;CMD_SELECT,CMD_UPDATE,etc.*/PlannedStmt*plannedstmt;/*已规划的语句,规划器的输出;planner'soutput(couldbeutility,too)*/constchar*sourceText;/*源SQL文本;sourcetextofthequery*/Snapshotsnapshot;/*查询使用的快照;snapshottouseforquery*/Snapshotcrosscheck_snapshot;/*RI更新/删除交叉检查快照;crosscheckforRIupdate/delete*/DestReceiver*dest;/*元组输出的接收器;thedestinationfortupleoutput*/ParamListInfoparams;/*需传入的参数值;paramvaluesbeingpassedin*/QueryEnvironment*queryEnv;/*查询环境变量;queryenvironmentpassedin*/intinstrument_options;/*InstrumentOption选项;ORofInstrumentOptionflags*//*ThesefieldsaresetbyExecutorStart*///以下变量由ExecutorStart函数设置TupleDesctupDesc;/*结果元组tuples描述;descriptorforresulttuples*/EState*estate;/*执行器状态;executor'squery-widestate*/PlanState*planstate;/*per-plan-node状态树;treeofper-plan-nodestate*//*ThisfieldissetbyExecutorRun*///以下变量由ExecutorRun设置boolalready_executed;/*先前已执行,则为T;trueifpreviouslyexecuted*//*ThisisalwayssetNULLbythecoresystem,butpluginscanchangeit*///内核设置为NULL,可由插件修改structInstrumentation*totaltime;/*ExecutorRun函数所花费的时间;totaltimespentinExecutorRun*/}QueryDesc;二、源码解读

PortalRun->PortalRunSelect函数执行以PORTAL_ONE_SELECT模式运行的SQL.

/**PortalRunSelect*Executeaportal'squeryinPORTAL_ONE_SELECTmode,andalso*whenfetchingfromacompletedholdStoreinPORTAL_ONE_RETURNING,*PORTAL_ONE_MOD_WITH,andPORTAL_UTIL_SELECTcases.*执行以PORTAL_ONE_SELECT模式运行的SQL,同时处理PORTAL_ONE_RETURNING/*PORTAL_ONE_MOD_WITH/PORTAL_UTIL_SELECT这几种模式下完成holdStore后的数据提取**ThishandlessimpleN-rows-forward-or-backwardcases.Formorecomplex*nonsequentialaccesstoaportal,seePortalRunFetch.*这将处理简单的n行前向或后向情况。*有关对门户的更复杂的非顺序访问,请参阅PortalRunFetch。**count<=0isinterpretedasano-op:thedestinationgetsstartedup*andshutdown,butnothingelsehappens.Also,count==FETCH_ALLis*interpretedas"allrows".(cfFetchStmt.howMany)*count<=0被解释为一个no-op:目标启动并关闭,但是没有发生其他事情。*另外,count==FETCH_ALL被解释为“所有行”。(cfFetchStmt.howMany)**CallermustalreadyhavevalidatedthePortalanddoneappropriate*setup(cf.PortalRun).*调用者必须完成Portal的校验以及相关的配置.**Returnsnumberofrowsprocessed(suitableforuseinresulttag)*返回已处理的行数.*/staticuint64PortalRunSelect(Portalportal,boolforward,longcount,DestReceiver*dest){QueryDesc*queryDesc;ScanDirectiondirection;uint64nprocessed;/**NB:queryDescwillbeNULLifwearefetchingfromaheldcursorora*completedutilityquery;can'tuseitinthatpath.*注意:从已持有的游标或者已完成的工具类查询中返回时,queryDesc有可能是NULL.*/queryDesc=portal->queryDesc;/*Callermessedupifwehaveneitherareadyquerynorhelddata.*///确保queryDescbuweiNULL或者持有提取的数据Assert(queryDesc||portal->holdStore);/**ForcethequeryDescdestinationtotherightthing.Thissupports*MOVE,forexample,whichwillpassindest=DestNone.Thisisokayto*changeaslongaswedoitoneveryfetch.(Themustnot*assumethatdestneverchanges.)*确保queryDesc目的地是正确的地方。*例如,它支持MOVE,它将传入dest=DestNone。*只要在每次取回时都这样做,这是可以改变的。(Executor不能假定dest永不改变。)*/if(queryDesc)queryDesc->dest=dest;//设置dest/**Determinewhichdirectiontogoin,andchecktoseeifwe'realready*attheendoftheavailabletuplesinthatdirection.Ifso,setthe*directiontoNoMovementtoavoidtryingtofetchanytuples.(This*checkexistsbecausenotallplannodetypesarerobustaboutbeing*calledagainifthey'vealreadyreturnedNULLonce.)Thencallthe*executor(wemustnotskipthis,becausethedestinationneedstoseea*setupandshutdownevenifnotuplesareavailable).Finally,update*theportalpositionstatedependingonthenumberoftuplesthatwere*retrieved.*确定要进入的方向,并检查是否已经在该方向的可用元组的末尾。*如果是这样,则将方向设置为NoMovement,以避免试图再次获取任何元组。*(之所以存在这种检查,是因为不是所有的计划节点类型都能够在已经返回NULL时再次调用。)*然后调用executor(我们不能跳过这一步,因为目标需要看到设置和关闭,即使没有元组可用)。*最后,根据检索到的元组数量更新Portal的数据位置状态。*/if(forward)//前向{if(portal->atEnd||count<=0){//已到末尾或者行计数小于等于0direction=NoMovementScanDirection;count=0;/*don'tpassnegativecounttoexecutor*/}elsedirection=ForwardScanDirection;//前向扫描/*Intheexecutor,zerocountprocessesallrows*///在executor中,count=0意味着提取所有行if(count==FETCH_ALL)count=0;if(portal->holdStore)//持有提取后的数据游标nprocessed=RunFromStore(portal,direction,(uint64)count,dest);else{//没有持有游标(数据)PushActiveSnapshot(queryDesc->snapshot);//快照入栈ExecutorRun(queryDesc,direction,(uint64)count,portal->run_once);//开始执行nprocessed=queryDesc->estate->es_processed;//结果行数PopActiveSnapshot();//快照出栈}if(!ScanDirectionIsNoMovement(direction))//扫描方向可移动{if(nprocessed>0)//扫描行数>0portal->atStart=false;/*可以向前移动了;OKtogobackwardnow*/if(count==0||nprocessed<(uint64)count)//count为0或者行数小于传入的计数器portal->atEnd=true;/*已完成扫描;weretrieved'emall*/portal->portalPos+=nprocessed;//位置移动(+处理行数)}}else//非前向(后向){if(portal->cursorOptions&CURSOR_OPT_NO_SCROLL)//如游标不可移动,报错ereport(ERROR,(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),errmsg("cursorcanonlyscanforward"),errhint("DeclareitwithSCROLLoptiontoenablebackwardscan.")));if(portal->atStart||count<=0){//处于开始或者count小于等于0direction=NoMovementScanDirection;count=0;/*don'tpassnegativecounttoexecutor*/}else//往后扫描direction=BackwardScanDirection;/*Intheexecutor,zerocountprocessesallrows*///参见forward=T的注释if(count==FETCH_ALL)count=0;if(portal->holdStore)nprocessed=RunFromStore(portal,direction,(uint64)count,dest);else{PushActiveSnapshot(queryDesc->snapshot);ExecutorRun(queryDesc,direction,(uint64)count,portal->run_once);nprocessed=queryDesc->estate->es_processed;PopActiveSnapshot();}if(!ScanDirectionIsNoMovement(direction)){if(nprocessed>0&&portal->atEnd){portal->atEnd=false;/*OKtogoforwardnow*/portal->portalPos++;/*adjustforendpointcase*/}if(count==0||nprocessed<(uint64)count){portal->atStart=true;/*weretrieved'emall*/portal->portalPos=0;}else{portal->portalPos-=nprocessed;}}}returnnprocessed;}/**RunFromStore*Fetchtuplesfromtheportal'stuplestore.*从Portal的tuplestore中提取元组.**CallingconventionsaresimilartoExecutorRun,exceptthatwe*donotdependonhavingaqueryDescorestate.Thereforewereturnthe*numberoftuplesprocessedastheresult,notinestate->es_processed.*该函数的调用约定类似于ExecutorRun,只是不依赖于是否拥有queryDesc或estate。*因此,返回处理的元组的数量作为结果,而不是在estate->es_processed中返回。**OnedifferencefromExecutorRunisthatthedestinationreceiverfunctions*areruninthecaller'smemorycontext(sincewehavenoestate).Watch*outformemoryleaks.*与ExecutorRun不同的是,目标接收器函数在调用者的内存上下文中运行(因为没有estate)。*需注意内存泄漏!!!*/staticuint64RunFromStore(Portalportal,ScanDirectiondirection,uint64count,DestReceiver*dest){uint64current_tuple_count=0;TupleTableSlot*slot;//元组表slotslot=MakeSingleTupleTableSlot(portal->tupDesc);dest->rStartup(dest,CMD_SELECT,portal->tupDesc);//目标启动if(ScanDirectionIsNoMovement(direction))//无法移动{/*donothingexceptstart/stopthedestination*///不需要做任何事情}else{boolforward=ScanDirectionIsForward(direction);//是否前向扫描for(;;)//循环{MemoryContextoldcontext;//内存上下文boolok;oldcontext=MemoryContextSwitchTo(portal->holdContext);//切换至相应的内存上下文ok=tuplestore_gettupleslot(portal->holdStore,forward,false,slot);//获取元组MemoryContextSwitchTo(oldcontext);//切换回原上下文if(!ok)break;//如出错,则跳出循环/**Ifwearenotabletosendthetuple,weassumethedestination*hasclosedandnomoretuplescanbesent.Ifthat'sthecase,*endtheloop.*如果不能发送元组到目标端,那么我们假设目标端已经关闭,不能发送更多元组。*如果是这样,结束循环。*/if(!dest->receiveSlot(slot,dest))break;ExecClearTuple(slot);//执行清理/**checkourtuplecount..ifwe'veprocessedthepropernumber*thenquit,elseloopagainandprocessmoretuples.Zerocount*meansnolimit.*检查元组计数…如果处理了正确的计数,那么退出,*否则再次循环并处理更多元组。零计数意味着没有限制。*/current_tuple_count++;if(count&&count==current_tuple_count)break;}}dest->rShutdown(dest);//关闭目标端ExecDropSingleTupleTableSlot(slot);//清除slotreturncurrent_tuple_count;//返回行数}/*----------------------------------------------------------------*ExecutorRun*ExecutorRun函数**Thisisthemainroutineoftheexecutormodule.Itaccepts*thequerydescriptorfromthetrafficcopandexecutesthe*queryplan.*这是executor模块的主要实现例程。它接受trafficcop的查询描述符并执行查询计划。**ExecutorStartmusthavebeencalledalready.*在此之前,已调用ExecutorStart函数.**IfdirectionisNoMovementScanDirectionthennothingisdone*excepttostartup/shutdownthedestination.Otherwise,*weretrieveupto'count'tuplesinthespecifieddirection.*如果方向是NoMovementScanDirection,那么除了启动/关闭目标之外什么也不做。*否则,在指定的方向上检索指定数量“count”的元组。**Note:count=0isinterpretedasnoportallimit,i.e.,runto*completion.Alsonotethatthecountlimitisonlyappliedto*retrievedtuples,notforinstancetothoseinserted/updated/deleted*byaModifyTableplannode.*注意:count=0被解释为没有限制,即,运行到完成。*还要注意,计数限制只适用于检索到的元组,而不适用于由ModifyTable计划节点插入/更新/删除的元组。**Thereisnoreturnvalue,butoutputtuples(ifany)aresentto*thedestinationreceiverspecifiedintheQueryDesc;andthenumber*oftuplesprocessedatthetoplevelcanbefoundin*estate->es_processed.*没有返回值,但是输出元组(如果有的话)被发送到QueryDesc中指定的目标接收器;*在顶层处理的元组数量可以在estate->es_processing中找到。**Weprovideafunctionhookvariablethatletsloadableplugins*getcontrolwhenExecutorRuniscalled.Suchapluginwould*normallycallstandard_ExecutorRun().*我们提供了一个钩子函数变量,可以让插件在调用ExecutorRun时获得控制权。*这样的插件通常会调用standard_ExecutorRun()函数。**----------------------------------------------------------------*/voidExecutorRun(QueryDesc*queryDesc,ScanDirectiondirection,uint64count,boolexecute_once){if(ExecutorRun_hook)(*ExecutorRun_hook)(queryDesc,direction,count,execute_once);//钩子函数elsestandard_ExecutorRun(queryDesc,direction,count,execute_once);//标准函数}voidstandard_ExecutorRun(QueryDesc*queryDesc,ScanDirectiondirection,uint64count,boolexecute_once){EState*estate;//全局执行状态CmdTypeoperation;//命令类型DestReceiver*dest;//接收器boolsendTuples;//是否需要传输元组MemoryContextoldcontext;//内存上下文/*sanitychecks*/Assert(queryDesc!=NULL);//校验queryDesc不能为NULLestate=queryDesc->estate;//获取执行状态Assert(estate!=NULL);//执行状态不能为NULLAssert(!(estate->es_top_eflags&EXEC_FLAG_EXPLAIN_ONLY));//eflags标记不能为EXEC_FLAG_EXPLAIN_ONLY/**Switchintoper-querymemorycontext*切换内存上下文*/oldcontext=MemoryContextSwitchTo(estate->es_query_cxt);/*AllowinstrumentationofExecutoroverallruntime*///允许全程instrumentationif(queryDesc->totaltime)InstrStartNode(queryDesc->totaltime);/**extractinformationfromthequerydescriptorandthequeryfeature.*从查询描述符和查询特性中提取信息。*/operation=queryDesc->operation;dest=queryDesc->dest;/**startuptuplereceiver,ifwewillbeemittingtuples*如需发送元组,则启动元组接收器*/estate->es_processed=0;estate->es_lastoid=InvalidOid;sendTuples=(operation==CMD_SELECT||queryDesc->plannedstmt->hasReturning);if(sendTuples)//如需发送元组dest->rStartup(dest,operation,queryDesc->tupDesc);/**runplan*执行Plan*/if(!ScanDirectionIsNoMovement(direction))//如非ScanDirectionIsNoMovement{if(execute_once&&queryDesc->already_executed)//校验elog(ERROR,"can'tre-executequeryflaggedforsingleexecution");queryDesc->already_executed=true;//修改标记ExecutePlan(estate,queryDesc->planstate,queryDesc->plannedstmt->parallelModeNeeded,operation,sendTuples,count,direction,dest,execute_once);//执行Plan}/**shutdowntuplereceiver,ifwestartedit*如启动了元组接收器,则关闭它*/if(sendTuples)dest->rShutdown(dest);if(queryDesc->totaltime)//收集时间InstrStopNode(queryDesc->totaltime,estate->es_processed);MemoryContextSwitchTo(oldcontext);//切换内存上下文}三、跟踪分析

测试脚本如下

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,设置断点,进入PortalRunSelect

(gdb)bPortalRunSelectBreakpoint1at0x8cc0e8:filepquery.c,line888.(gdb)cContinuing.Breakpoint1,PortalRunSelect(portal=0x1af2468,forward=true,count=9223372036854775807,dest=0x1b74668)atpquery.c:888warning:Sourcefileismorerecentthanexecutable.888queryDesc=portal->queryDesc;(gdb)

查看输入参数portal&dest,forward为T表示前向扫描
portal:未命名的Portal,holdStore为NULL,atStart = true, atEnd = false, portalPos = 0
dest:接收器slot为printtup

(gdb)p*portal$1={name=0x1af5e90"",prepStmtName=0x0,portalContext=0x1b795d0,resowner=0x1abde80,cleanup=0x6711b6<PortalCleanup>,createSubid=1,activeSubid=1,sourceText=0x1a8ceb8"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>...,commandTag=0xc5eed5"SELECT",stmts=0x1b74630,cplan=0x0,portalParams=0x0,queryEnv=0x0,strategy=PORTAL_ONE_SELECT,cursorOptions=4,run_once=true,status=PORTAL_ACTIVE,portalPinned=false,autoHeld=false,queryDesc=0x1b796e8,tupDesc=0x1b867d8,formats=0x1b79780,holdStore=0x0,holdContext=0x0,holdSnapshot=0x0,atStart=true,atEnd=false,portalPos=0,creation_time=595566906253867,visible=false}(gdb)p*dest$2={receiveSlot=0x48cc00<printtup>,rStartup=0x48c5c1<printtup_startup>,rShutdown=0x48d02e<printtup_shutdown>,rDestroy=0x48d0a7<printtup_destroy>,mydest=DestRemote}

校验并设置dest

(gdb)n891Assert(queryDesc||portal->holdStore);(gdb)899if(queryDesc)(gdb)900queryDesc->dest=dest;

前向扫描

(gdb)n913if(forward)(gdb)915if(portal->atEnd||count<=0)

进入ExecutorRun

...(gdb)932ExecutorRun(queryDesc,direction,(uint64)count,(gdb)stepExecutorRun(queryDesc=0x1b796e8,direction=ForwardScanDirection,count=0,execute_once=true)atexecMain.c:304warning:Sourcefileismorerecentthanexecutable.304if(ExecutorRun_hook)

进入standard_ExecutorRun

(gdb)n307standard_ExecutorRun(queryDesc,direction,count,execute_once);(gdb)stepstandard_ExecutorRun(queryDesc=0x1b796e8,direction=ForwardScanDirection,count=0,execute_once=true)atexecMain.c:321321Assert(queryDesc!=NULL);

standard_ExecutorRun->校验并切换上下文

321Assert(queryDesc!=NULL);(gdb)n323estate=queryDesc->estate;(gdb)325Assert(estate!=NULL);(gdb)326Assert(!(estate->es_top_eflags&EXEC_FLAG_EXPLAIN_ONLY));(gdb)331oldcontext=MemoryContextSwitchTo(estate->es_query_cxt);(gdb)

standard_ExecutorRun->变量赋值,判断是否需要传输元组

(gdb)334if(queryDesc->totaltime)(gdb)n340operation=queryDesc->operation;(gdb)341dest=queryDesc->dest;(gdb)poperation$3=CMD_SELECT(gdb)n346estate->es_processed=0;(gdb)347estate->es_lastoid=InvalidOid;(gdb)349sendTuples=(operation==CMD_SELECT||(gdb)352if(sendTuples)(gdb)353dest->rStartup(dest,operation,queryDesc->tupDesc);(gdb)psendTuples$4=true(gdb)

standard_ExecutorRun->执行计划(ExecutePlan函数下节介绍)

(gdb)n358if(!ScanDirectionIsNoMovement(direction))(gdb)360if(execute_once&&queryDesc->already_executed)(gdb)362queryDesc->already_executed=true;(gdb)364ExecutePlan(estate,(gdb)

standard_ExecutorRun->关闭资源并切换上下文

(gdb)378if(sendTuples)(gdb)n379dest->rShutdown(dest);(gdb)381if(queryDesc->totaltime)(gdb)384MemoryContextSwitchTo(oldcontext);(gdb)385}(gdb)

standard_ExecutorRun->回到PortalRunSelect

(gdb)nExecutorRun(queryDesc=0x1b796e8,direction=ForwardScanDirection,count=0,execute_once=true)atexecMain.c:308308}(gdb)PortalRunSelect(portal=0x1af2468,forward=true,count=0,dest=0x1b74668)atpquery.c:934934nprocessed=queryDesc->estate->es_processed;

快照出栈,修改状态atStart/atEnd等

(gdb)n935PopActiveSnapshot();(gdb)938if(!ScanDirectionIsNoMovement(direction))(gdb)940if(nprocessed>0)(gdb)pnprocessed$6=99991(gdb)n941portal->atStart=false;/*OKtogobackwardnow*/(gdb)942if(count==0||nprocessed<(uint64)count)(gdb)

完成调用

(gdb)n943portal->atEnd=true;/*weretrieved'emall*/(gdb)pcount$7=0(gdb)n944portal->portalPos+=nprocessed;(gdb)997returnnprocessed;(gdb)998}(gdb)nPortalRun(portal=0x1af2468,count=9223372036854775807,isTopLevel=true,run_once=true,dest=0x1b74668,altdest=0x1b74668,completionTag=0x7ffc5ff58740"")atpquery.c:780780if(completionTag&&portal->commandTag)(gdb)pnprocessed$8=99991

“PostgreSQL中PortalRun->PortalRunSelect函数的实现逻辑是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!