这篇文章主要为大家展示了“APPEND Plan Node节点的初始化和执行逻辑”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“APPEND Plan Node节点的初始化和执行逻辑”这篇文章吧。

一、数据结构

AppendState
用于Append Node执行的数据结构

/*----------------*AppendStateinformation*AppendState结构体**nplanshowmanyplansareinthearray*数组大小*whichplanwhichplanisbeingexecuted(0..n-1),ora*specialnegativevalue.SeenodeAppend.c.*那个计划将要被/正在被执行(0..n-1),或者一个特别的负数,详见nodeAppend.c*pruningstatedetailsrequiredtoallowpartitionstobe*eliminatedfromthescan,orNULLifnotpossible.*在扫描过程中允许忽略分区的细节信息,入不可能则为NULL*valid_subplansforruntimepruning,validappendplansindexesto*scan.*用于运行期裁剪分区,将要扫描的有效appendplans索引*----------------*/structAppendState;typedefstructAppendStateAppendState;structParallelAppendState;typedefstructParallelAppendStateParallelAppendState;structPartitionPruneState;structAppendState{//第一个字段为NodeTagPlanStateps;/*itsfirstfieldisNodeTag*///PlanStates数组PlanState**appendplans;/*arrayofPlanStatesformyinputs*///数组大小intas_nplans;//那个计划将要被/正在被执行intas_whichplan;//包含第一个部分计划的appendplans数组编号intas_first_partial_plan;/*Indexof'appendplans'containing*thefirstpartialplan*///并行执行的协调信息ParallelAppendState*as_pstate;/*parallelcoordinationinfo*///协调信息大小Sizepstate_len;/*sizeofparallelcoordinationinfo*///分区裁剪信息structPartitionPruneState*as_prune_state;//有效的子计划位图集Bitmapset*as_valid_subplans;//选择下个子计划的实现函数bool(*choose_next_subplan)(AppendState*);};二、源码解读

ExecInitAppend
ExecInitAppend函数为开始append node的所有子关系扫描执行相关的初始化.

/*----------------------------------------------------------------*ExecInitAppend**Beginallofthesubscansoftheappendnode.*开始appendnode的所有子关系扫描**(Thisispotentiallywasteful,sincetheentireresultofthe*appendnodemaynotbescanned,butthiswayallofthe*structuresgetallocatedintheexecutor'stoplevelmemory*blockinsteadofthatofthecalltoExecAppend.)*(这可能是一种浪费,因为可能不会扫描append节点的整个结果,*但是通过这种方式,所有结构都将在执行程序的最顶级内存块中分配,*而不是在ExecAppend调用的内存块中分配。)*----------------------------------------------------------------*/AppendState*ExecInitAppend(Append*node,EState*estate,inteflags){AppendState*appendstate=makeNode(AppendState);PlanState**appendplanstates;Bitmapset*validsubplans;intnplans;intfirstvalid;inti,j;ListCell*lc;/*checkforunsupportedflags*///检查不支持的标记Assert(!(eflags&EXEC_FLAG_MARK));/**createnewAppendStateforourappendnode*为append节点创建AppendState*/appendstate->ps.plan=(Plan*)node;appendstate->ps.state=estate;appendstate->ps.ExecProcNode=ExecAppend;/*Letchoose_next_subplan_*functionhandlesettingthefirstsubplan*///让choose_next_subplan_*函数处理第一个子计划的设置appendstate->as_whichplan=INVALID_SUBPLAN_INDEX;/*Ifrun-timepartitionpruningisenabled,thensetthatupnow*///如允许运行时分区裁剪,则配置分区裁剪if(node->part_prune_info!=NULL){PartitionPruneState*prunestate;/*Wemayneedanexpressioncontexttoevaluatepartitionexprs*///需要独立的表达式上下文来解析分区表达式ExecAssignExprContext(estate,&appendstate->ps);/*Createtheworkingdatastructureforpruning.*///为裁剪创建工作数据结构信息prunestate=ExecCreatePartitionPruneState(&appendstate->ps,node->part_prune_info);appendstate->as_prune_state=prunestate;/*Performaninitialpartitionprune,ifrequired.*///如需要,执行初始的分区裁剪if(prunestate->do_initial_prune){/*Determinewhichsubplanssurviveinitialpruning*///确定那个子计划在初始裁剪中仍"存活"validsubplans=ExecFindInitialMatchingSubPlans(prunestate,list_length(node->appendplans));/**Thecasewherenosubplanssurvivepruningmustbehandled*specially.Theproblemhereisthatcodeinexplain.crequires*anAppendtohaveatleastonesubplaninorderforitto*properlydeterminetheVarsinthatsubplan'stargetlist.We*sidestepthisissuebyjustinitializingthefirstsubplanand*settingas_whichplantoNO_MATCHING_SUBPLANStoindicatethat*wedon'treallyneedtoscananysubnodes.*没有子计划"存活"的情况需要特别处理.*这里的问题是explain.c的执行逻辑至少需要一个Append节点和一个子计划,*用于确定子计划投影列链表中的Vars.*通过初始化第一个子计划以及设置as_whichplan为NO_MATCHING_SUBPLANS,*用以指示不需要扫描任何的子节点.*/if(bms_is_empty(validsubplans)){appendstate->as_whichplan=NO_MATCHING_SUBPLANS;/*Markthefirstasvalidsothatit'sinitializedbelow*///标记第一个子计划为有效的,以便在接下来可以初始化validsubplans=bms_make_singleton(0);}//子计划数目nplans=bms_num_members(validsubplans);}else{/*We'llneedtoinitializeallsubplans*///需要初始化所有的的子计划nplans=list_length(node->appendplans);Assert(nplans>0);validsubplans=bms_add_range(NULL,0,nplans-1);}/**Ifnoruntimepruningisrequired,wecanfillas_valid_subplans*immediately,preventinglatercallstoExecFindMatchingSubPlans.*如不需要运行期裁剪,那么我们可以马上设置as_valid_subplans,*以防止后续逻辑调用ExecFindMatchingSubPlans*/if(!prunestate->do_exec_prune){Assert(nplans>0);appendstate->as_valid_subplans=bms_add_range(NULL,0,nplans-1);}}else{//不需要运行期裁剪nplans=list_length(node->appendplans);/**Whenrun-timepartitionpruningisnotenabledwecanjustmarkall*subplansasvalid;theymustalsoallbeinitialized.*运行期裁剪不允许,设置所有的子计划为有效,同时这些子计划必须全部初始化*/Assert(nplans>0);appendstate->as_valid_subplans=validsubplans=bms_add_range(NULL,0,nplans-1);appendstate->as_prune_state=NULL;}/**Initializeresulttupletypeandslot.*初始化结果元组类型和slot*/ExecInitResultTupleSlotTL(&appendstate->ps,&TTSOpsVirtual);/*nodereturnsslotsfromeachofitssubnodes,thereforenotfixed*///从每一个子节点中返回slot,但并不固定appendstate->ps.resultopsset=true;appendstate->ps.resultopsfixed=false;appendplanstates=(PlanState**)palloc(nplans*sizeof(PlanState*));/**callExecInitNodeoneachofthevalidplanstobeexecutedandsave*theresultsintotheappendplanstatesarray.*在每一个有效的计划上执行ExecInitNode,*同时保存结果到appendplanstates数组中.**Whileatit,findoutthefirstvalidpartialplan.*在此期间,找到第一个有效的部分计划(并行使用)*/j=i=0;firstvalid=nplans;foreach(lc,node->appendplans){if(bms_is_member(i,validsubplans)){Plan*initNode=(Plan*)lfirst(lc);/**Recordthelowestappendplansindexwhichisavalidpartial*plan.*记录有效部分计划的最小appendplans索引号*/if(i>=node->first_partial_plan&&j<firstvalid)firstvalid=j;appendplanstates[j++]=ExecInitNode(initNode,estate,eflags);//初始化节点}i++;}//配置AppendStateappendstate->as_first_partial_plan=firstvalid;appendstate->appendplans=appendplanstates;appendstate->as_nplans=nplans;/**Miscellaneousinitialization*初始化*/appendstate->ps.ps_ProjInfo=NULL;/*Forparallelquery,thiswillbeoverriddenlater.*///对于并行查询,该值后面会被覆盖appendstate->choose_next_subplan=choose_next_subplan_locally;returnappendstate;}

ExecAppend
ExecAppend在多个子计划中进行迭代处理

/*----------------------------------------------------------------*ExecAppend**Handlesiterationovermultiplesubplans.*在多个子计划中处理迭代*----------------------------------------------------------------*/staticTupleTableSlot*ExecAppend(PlanState*pstate){AppendState*node=castNode(AppendState,pstate);if(node->as_whichplan<0){/**Ifnosubplanhasbeenchosen,wemustchooseonebefore*proceeding.*如果没有子计划选中,必须在开始前选择一个*/if(node->as_whichplan==INVALID_SUBPLAN_INDEX&&!node->choose_next_subplan(node))returnExecClearTuple(node->ps.ps_ResultTupleSlot);//出错,则清除tuple,返回/*Nothingtodoiftherearenomatchingsubplans*///如无匹配的子计划,返回elseif(node->as_whichplan==NO_MATCHING_SUBPLANS)returnExecClearTuple(node->ps.ps_ResultTupleSlot);}for(;;)//循环获取tupleslot{PlanState*subnode;TupleTableSlot*result;CHECK_FOR_INTERRUPTS();/**figureoutwhichsubplanwearecurrentlyprocessing*选择哪个子计划需要现在执行*/Assert(node->as_whichplan>=0&&node->as_whichplan<node->as_nplans);subnode=node->appendplans[node->as_whichplan];/**getatuplefromthesubplan*从子计划中获取tuple*/result=ExecProcNode(subnode);if(!TupIsNull(result)){/**Ifthesubplangaveussomethingthenreturnitas-is.Wedo*NOTmakeuseoftheresultslotthatwassetupin*ExecInitAppend;there'snoneedforit.*如果子计划有返回,则直接返回.*在这里,不需要在ExecInitAppend中构造结果slot,因为不需要这样做.*/returnresult;}/*choosenewsubplan;ifnone,we'redone*///选择新的子计划,如无,则完成调用if(!node->choose_next_subplan(node))returnExecClearTuple(node->ps.ps_ResultTupleSlot);}}三、跟踪分析

测试脚本如下

testdb=#explainverboseselect*fromt_hash_partitionwherec1=1ORc1=2;QUERYPLAN-------------------------------------------------------------------------------------Append(cost=0.00..30.53rows=6width=200)->SeqScanonpublic.t_hash_partition_1(cost=0.00..15.25rows=3width=200)Output:t_hash_partition_1.c1,t_hash_partition_1.c2,t_hash_partition_1.c3Filter:((t_hash_partition_1.c1=1)OR(t_hash_partition_1.c1=2))->SeqScanonpublic.t_hash_partition_3(cost=0.00..15.25rows=3width=200)Output:t_hash_partition_3.c1,t_hash_partition_3.c2,t_hash_partition_3.c3Filter:((t_hash_partition_3.c1=1)OR(t_hash_partition_3.c1=2))(7rows)

ExecInitAppend
启动gdb,设置断点

(gdb)bExecInitAppendBreakpoint1at0x6efa8a:filenodeAppend.c,line103.(gdb)cContinuing.Breakpoint1,ExecInitAppend(node=0x27af638,estate=0x27be058,eflags=16)atnodeAppend.c:103103AppendState*appendstate=makeNode(AppendState);

初始化AppendState

(gdb)n113Assert(!(eflags&EXEC_FLAG_MARK));(gdb)119ExecLockNonLeafAppendTables(node->partitioned_rels,estate);(gdb)124appendstate->ps.plan=(Plan*)node;(gdb)125appendstate->ps.state=estate;(gdb)126appendstate->ps.ExecProcNode=ExecAppend;(gdb)129appendstate->as_whichplan=INVALID_SUBPLAN_INDEX;(gdb)

不需要运行期分区裁剪

(gdb)132if(node->part_prune_info!=NULL)(gdb)pnode->part_prune_info$1=(structPartitionPruneInfo*)0x0(gdb)

需要初始化所有的的子计划

(gdb)n190nplans=list_length(node->appendplans);(gdb)196Assert(nplans>0);(gdb)198bms_add_range(NULL,0,nplans-1);(gdb)197appendstate->as_valid_subplans=validsubplans=(gdb)n199appendstate->as_prune_state=NULL;(gdb)p*validsubplans$4={nwords=1,words=0x27be38c}(gdb)p*validsubplans->words$5=3-->即No.0+No.1(gdb)

初始化结果元组类型和slot

(gdb)n205ExecInitResultTupleSlotTL(estate,&appendstate->ps);(gdb)207appendplanstates=(PlanState**)palloc(nplans*(gdb)

在每一个有效的计划上执行ExecInitNode,同时保持结果到appendplanstates数组中.

(gdb)216j=i=0;(gdb)n217firstvalid=nplans;(gdb)218foreach(lc,node->appendplans)(gdb)pnplans$6=2(gdb)pnode->appendplans$7=(List*)0x27b30f0(gdb)p*node->appendplans$8={type=T_List,length=2,head=0x27b30c8,tail=0x27b33d8}(gdb)

遍历appendplans,初始化appendplans中的节点(SeqScan)

(gdb)n220if(bms_is_member(i,validsubplans))(gdb)n222Plan*initNode=(Plan*)lfirst(lc);(gdb)228if(i>=node->first_partial_plan&&j<firstvalid)(gdb)231appendplanstates[j++]=ExecInitNode(initNode,estate,eflags);(gdb)pj$9=0(gdb)pi$10=0(gdb)n233i++;(gdb)218foreach(lc,node->appendplans)(gdb)220if(bms_is_member(i,validsubplans))(gdb)222Plan*initNode=(Plan*)lfirst(lc);(gdb)228if(i>=node->first_partial_plan&&j<firstvalid)(gdb)p*initNode$11={type=T_SeqScan,startup_cost=0,total_cost=15.25,plan_rows=3,plan_width=200,parallel_aware=false,parallel_safe=true,plan_node_id=2,targetlist=0x27b31a8,qual=0x27b3308,lefttree=0x0,righttree=0x0,initPlan=0x0,extParam=0x0,allParam=0x0}(gdb)n231appendplanstates[j++]=ExecInitNode(initNode,estate,eflags);(gdb)233i++;(gdb)218foreach(lc,node->appendplans)(gdb)236appendstate->as_first_partial_plan=firstvalid;(gdb)

完成初始化,其中choose_next_subplan函数为choose_next_subplan_locally函数

(gdb)pfirstvalid$12=2(gdb)n237appendstate->appendplans=appendplanstates;(gdb)238appendstate->as_nplans=nplans;(gdb)244appendstate->ps.ps_ProjInfo=NULL;(gdb)247appendstate->choose_next_subplan=choose_next_subplan_locally;(gdb)249returnappendstate;(gdb)pchoose_next_subplan_locally$13={_Bool(AppendState*)}0x6f02d8<choose_next_subplan_locally>(gdb)p*appendstate$15={ps={type=T_AppendState,plan=0x27af638,state=0x27be058,ExecProcNode=0x6efe19<ExecAppend>,ExecProcNodeReal=0x0,instrument=0x0,worker_instrument=0x0,worker_jit_instrument=0x0,qual=0x0,lefttree=0x0,righttree=0x0,initPlan=0x0,subPlan=0x0,chgParam=0x0,ps_ResultTupleSlot=0x27be5c0,ps_ExprContext=0x0,ps_ProjInfo=0x0,scandesc=0x0},appendplans=0x27be6b8,as_nplans=2,as_whichplan=-1,as_first_partial_plan=2,as_pstate=0x0,pstate_len=0,as_prune_state=0x0,as_valid_subplans=0x27be388,choose_next_subplan=0x6f02d8<choose_next_subplan_locally>}

ExecAppend
设置断点,进入ExecAppend

(gdb)delDeleteallbreakpoints?(yorn)y(gdb)bExecAppendBreakpoint2at0x6efe2a:filenodeAppend.c,line261.(gdb)cContinuing.Breakpoint2,ExecAppend(pstate=0x27be270)atnodeAppend.c:261261AppendState*node=castNode(AppendState,pstate);

输入参数为先前已完成初始化的AppendState

(gdb)p*(AppendState*)pstate$19={ps={type=T_AppendState,plan=0x27af638,state=0x27be058,ExecProcNode=0x6efe19<ExecAppend>,ExecProcNodeReal=0x6efe19<ExecAppend>,instrument=0x0,worker_instrument=0x0,worker_jit_instrument=0x0,qual=0x0,lefttree=0x0,righttree=0x0,initPlan=0x0,subPlan=0x0,chgParam=0x0,ps_ResultTupleSlot=0x27be5c0,ps_ExprContext=0x0,ps_ProjInfo=0x0,scandesc=0x0},appendplans=0x27be6b8,as_nplans=2,as_whichplan=-1,as_first_partial_plan=2,as_pstate=0x0,pstate_len=0,as_prune_state=0x0,as_valid_subplans=0x27be388,choose_next_subplan=0x6f02d8<choose_next_subplan_locally>}

如果没有子计划选中,必须在开始前选择一个(选择子计划:No.0)

(gdb)n263if(node->as_whichplan<0)(gdb)269if(node->as_whichplan==INVALID_SUBPLAN_INDEX&&(gdb)270!node->choose_next_subplan(node))(gdb)269if(node->as_whichplan==INVALID_SUBPLAN_INDEX&&(gdb)274elseif(node->as_whichplan==NO_MATCHING_SUBPLANS)(gdb)pnode->as_whichplan$20=0(gdb)n283CHECK_FOR_INTERRUPTS();

获取子计划并执行

(gdb)288Assert(node->as_whichplan>=0&&node->as_whichplan<node->as_nplans);(gdb)289subnode=node->appendplans[node->as_whichplan];(gdb)294result=ExecProcNode(subnode);(gdb)p*subnode$21={type=T_SeqScanState,plan=0x27b2728,state=0x27be058,ExecProcNode=0x6e4bde<ExecProcNodeFirst>,ExecProcNodeReal=0x71578d<ExecSeqScan>,instrument=0x0,worker_instrument=0x0,worker_jit_instrument=0x0,qual=0x27bec88,lefttree=0x0,righttree=0x0,initPlan=0x0,subPlan=0x0,chgParam=0x0,ps_ResultTupleSlot=0x27bebc8,ps_ExprContext=0x27be7f8,ps_ProjInfo=0x0,scandesc=0x7f7d049417e0}

返回slot

(gdb)n296if(!TupIsNull(result))(gdb)p*result$22={type=T_TupleTableSlot,tts_isempty=false,tts_shouldFree=false,tts_shouldFreeMin=false,tts_slow=false,tts_tuple=0x27c5500,tts_tupleDescriptor=0x7f7d049417e0,tts_mcxt=0x27bdf40,tts_buffer=120,tts_nvalid=1,tts_values=0x27be950,tts_isnull=0x27be968,tts_mintuple=0x0,tts_minhdr={t_len=0,t_self={ip_blkid={bi_hi=0,bi_lo=0},ip_posid=0},t_tableOid=0,t_data=0x0},tts_off=4,tts_fixedTupleDescriptor=true}

如第一个子计划执行完毕,则选择下一个子计划(调用choose_next_subplan函数切换到下一个子计划)

(gdb)cContinuing.Breakpoint2,ExecAppend(pstate=0x27be270)atnodeAppend.c:261261AppendState*node=castNode(AppendState,pstate);(gdb)n263if(node->as_whichplan<0)(gdb)283CHECK_FOR_INTERRUPTS();(gdb)288Assert(node->as_whichplan>=0&&node->as_whichplan<node->as_nplans);(gdb)289subnode=node->appendplans[node->as_whichplan];(gdb)294result=ExecProcNode(subnode);(gdb)296if(!TupIsNull(result))(gdb)307if(!node->choose_next_subplan(node))(gdb)309}

以上是“APPEND Plan Node节点的初始化和执行逻辑”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!