PostgreSQL查询优化中对消除外连接的处理过程是什么
本篇内容介绍了“PostgreSQL查询优化中对消除外连接的处理过程是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
使用的测试脚本:
droptableifexistst_null1;createtablet_null1(c1int);insertintot_null1values(1);insertintot_null1values(2);insertintot_null1values(null);droptableifexistst_null2;createtablet_null2(c1int);insertintot_null2values(1);insertintot_null2values(null);一、基本概念
消除外连接的代码注释说明如下:
/**reduce_outer_joins*Attempttoreduceouterjoinstoplaininnerjoins.**Theideahereisthatgivenaquerylike*SELECT...FROMaLEFTJOINbON(...)WHEREb.y=42;*wecanreducetheLEFTJOINtoaplainJOINifthe"="operatorinWHERE*isstrict.ThestrictoperatorwillalwaysreturnNULL,causingtheouter*WHEREtofail,onanyrowwheretheLEFTJOINfilledinNULLsforb's*columns.Therefore,there'snoneedforthejointoproducenull-extended*rowsinthefirstplace---whichmakesitaplainjoinnotanouterjoin.*(Thisscenariomaynotbeverylikelyinaquerywrittenoutbyhand,but*it'sreasonablylikelywhenpushingqualsdownintocomplexviews.)**Moregenerally,anouterjoincanbereducedinstrengthifthereisa*strictqualaboveitinthequaltreethatconstrainsaVarfromthe*nullablesideofthejointobenon-null.(ForFULLjoinsthisapplies*toeachsideseparately.)**Anothertransformationweapplyhereistorecognizecaseslike*SELECT...FROMaLEFTJOINbON(a.x=b.y)WHEREb.yISNULL;*Ifthejoinclauseisstrictforb.y,thenonlynull-extendedrowscould*passtheupperWHERE,andwecanconcludethatwhatthequeryisreally*specifyingisananti-semijoin.WechangethejointypefromJOIN_LEFT*toJOIN_ANTI.TheISNULLclausethenbecomesredundant,andmustbe*removedtopreventbogusselectivitycalculations,butweleaveitto*distribute_qual_to_relstogetridofsuchclauses.**Also,wegetridofJOIN_RIGHTcasesbyflippingthemaroundtobecome*JOIN_LEFT.Thissavessomecodehereandinsomelaterplannerroutines,*butthemainreasontodoitistonotneedtoinventaJOIN_REVERSE_ANTI*jointype.**Toeaserecognitionofstrictqualclauses,werequirethisroutinetobe*runafterexpressionpreprocessing(i.e.,qualcanonicalizationandJOIN*alias-varexpansion).*/
有两种类型的外连接可以被消除,第一种是形如以下形式的语句:
SELECT ... FROM a LEFT JOIN b ON (...) WHERE b.y = 42;
这种语句如满足条件可变换为内连接(INNER_JOIN).
之所以可以变换为内连接,那是因为这样的语句与内连接处理的结果是一样的,原因是在Nullable-Side端(需要填充NULL值的一端),存在过滤条件保证这一端不可能是NULL值,比如IS NOT NULL/y = 42这类强(strict)过滤条件.
testdb=#explainverboseselect*fromt_null1aleftjoint_null2bona.c1=b.c1;QUERYPLAN--------------------------------------------------------------------------------MergeLeftJoin(cost=359.57..860.00rows=32512width=8)--外连接Output:a.c1,b.c1MergeCond:(a.c1=b.c1)->Sort(cost=179.78..186.16rows=2550width=4)Output:a.c1SortKey:a.c1->SeqScanonpublic.t_null1a(cost=0.00..35.50rows=2550width=4)Output:a.c1->Sort(cost=179.78..186.16rows=2550width=4)Output:b.c1SortKey:b.c1->SeqScanonpublic.t_null2b(cost=0.00..35.50rows=2550width=4)Output:b.c1(13rows)testdb=#explainverboseselect*fromt_null1aleftjoint_null2bona.c1=b.c1whereb.c1=1;QUERYPLAN------------------------------------------------------------------------------NestedLoop(cost=0.00..85.89rows=169width=8)--外连接(Left关键字)已被消除Output:a.c1,b.c1->SeqScanonpublic.t_null1a(cost=0.00..41.88rows=13width=4)Output:a.c1Filter:(a.c1=1)->Materialize(cost=0.00..41.94rows=13width=4)Output:b.c1->SeqScanonpublic.t_null2b(cost=0.00..41.88rows=13width=4)Output:b.c1Filter:(b.c1=1)(10rows)
第二种形如:
SELECT ... FROM a LEFT JOIN b ON (a.x = b.y) WHERE b.y IS NULL;
这种语句如满足条件可以变换为反半连接(ANTI-SEMIJOIN).
过滤条件已明确要求Nullable-Side端y IS NULL,如果连接条件是a.x = b.y这类严格(strict)的条件,那么这样的外连接与反半连接的结果是一样的.
testdb=#explainverboseselect*fromt_null1aleftjoint_null2bona.c1=b.c1whereb.c1isnull;QUERYPLAN--------------------------------------------------------------------------------HashAntiJoin(cost=67.38..152.44rows=1275width=8)--变换为反连接Output:a.c1,b.c1HashCond:(a.c1=b.c1)->SeqScanonpublic.t_null1a(cost=0.00..35.50rows=2550width=4)Output:a.c1->Hash(cost=35.50..35.50rows=2550width=4)Output:b.c1->SeqScanonpublic.t_null2b(cost=0.00..35.50rows=2550width=4)Output:b.c1(9rows)
值得一提的是,在PG中,形如SELECT ... FROM a LEFT JOIN b ON (...) WHERE b.y = 42;这样的SQL语句,WHERE b.y = 42这类条件可以视为连接的上层过滤条件,在查询树中,Jointree->fromlist(元素类型为JoinExpr)与Jointree->quals处于同一层次,由于JoinExpr中的quals为同层条件,因此其上层即为Jointree->quals.有兴趣的可以查看日志输出查看Query查询树结构.
二、源码解读消除外连接的代码在主函数subquery_planner中,通过调用reduce_outer_joins函数实现,代码片段如下:
/**Ifwehaveanyouterjoins,trytoreducethemtoplaininnerjoins.*Thisstepismosteasilydoneafterwe'vedoneexpression*preprocessing.*/if(hasOuterJoins)reduce_outer_joins(root);
reduce_outer_joins
相关的数据结构和依赖的子函数:
reduce_outer_joins_state
typedefstructreduce_outer_joins_state{Relidsrelids;/*baserelidswithinthissubtree*/boolcontains_outer;/*doessubtreecontainouterjoin(s)?*/List*sub_states;/*Listofstatesforsubtreecomponents*/}reduce_outer_joins_state;
BitmapXX
typedefstructBitmapset{intnwords;/*numberofwordsinarray*/bitmapwordwords[FLEXIBLE_ARRAY_MEMBER];/*really[nwords]*/}Bitmapset;#defineWORDNUM(x)((x)/BITS_PER_BITMAPWORD)#defineBITNUM(x)((x)%BITS_PER_BITMAPWORD)/*Theunitsizecanbeadjustedbychangingthesethreedeclarations:*/#defineBITS_PER_BITMAPWORD32typedefuint32bitmapword;/*mustbeanunsignedtype*//**bms_make_singleton-buildabitmapsetcontainingasinglemember*/Bitmapset*bms_make_singleton(intx){Bitmapset*result;intwordnum,bitnum;if(x<0)elog(ERROR,"negativebitmapsetmembernotallowed");wordnum=WORDNUM(x);bitnum=BITNUM(x);result=(Bitmapset*)palloc0(BITMAPSET_SIZE(wordnum+1));result->nwords=wordnum+1;result->words[wordnum]=((bitmapword)1<<bitnum);returnresult;}/**bms_add_member-addaspecifiedmembertoset**Inputsetismodifiedorrecycled!*/Bitmapset*bms_add_member(Bitmapset*a,intx){intwordnum,bitnum;if(x<0)elog(ERROR,"negativebitmapsetmembernotallowed");if(a==NULL)returnbms_make_singleton(x);wordnum=WORDNUM(x);bitnum=BITNUM(x);/*enlargethesetifnecessary*/if(wordnum>=a->nwords){intoldnwords=a->nwords;inti;a=(Bitmapset*)repalloc(a,BITMAPSET_SIZE(wordnum+1));a->nwords=wordnum+1;/*zeroouttheenlargedportion*/for(i=oldnwords;i<a->nwords;i++)a->words[i]=0;}a->words[wordnum]|=((bitmapword)1<<bitnum);returna;}
find_nonnullable_rels
/**find_nonnullable_rels*Determinewhichbaserelsareforcednonnullablebygivenclause.**ReturnsthesetofallRelidsthatarereferencedintheclauseinsuch*awaythattheclausecannotpossiblyreturnTRUEifanyoftheseRelids*isanall-NULLrow.(ItisOKtoerronthesideofconservatism;hence*theanalysishereissimplistic.)**Thesemanticsherearesubtlydifferentfromcontain_nonstrict_functions:*thatfunctionisconcernedwithNULLresultsfromarbitraryexpressions,*buthereweassumethattheinputisaBooleanexpression,andwishto*seeifNULLinputswillprovablycauseaFALSE-or-NULLresult.Weexpect*theexpressiontohavebeenAND/ORflattenedandconvertedtoimplicit-AND*format.**Note:thisfunctionislargelyduplicativeoffind_nonnullable_vars().*Thereasonnottosimplifythisfunctionintoathinwrapperaround*find_nonnullable_vars()isthatthetestedconditionsreallyaredifferent:*aclauselike"t1.v1ISNOTNULLORt1.v2ISNOTNULL"doesnotprove*thateitherv1orv2can'tbeNULL,butitdoesprovethatthet1row*asawholecan'tbeall-NULL.**top_levelistruewhilescanningtop-levelAND/ORstructure;here,showing*theresultiseitherFALSEorNULLisgoodenough.top_levelisfalsewhen*wehavedescendedbelowaNOTorastrictfunction:nowwemustbeableto*provethatthesubexpressiongoestoNULL.**Wedon'tuseexpression_tree_walkerherebecausewedon'twanttodescend*throughverymanykindsofnodes;onlytheoneswecanbesurearestrict.*/Relidsfind_nonnullable_rels(Node*clause){returnfind_nonnullable_rels_walker(clause,true);}staticRelidsfind_nonnullable_rels_walker(Node*node,booltop_level){Relidsresult=NULL;ListCell*l;if(node==NULL)returnNULL;if(IsA(node,Var)){Var*var=(Var*)node;if(var->varlevelsup==0)result=bms_make_singleton(var->varno);}elseif(IsA(node,List)){/**Attoplevel,weareexamininganimplicit-ANDlist:ifanyofthe*armsproducesFALSE-or-NULLthentheresultisFALSE-or-NULL.If*notattoplevel,weareexaminingtheargumentsofastrict*function:ifanyofthemproduceNULLthentheresultofthe*functionmustbeNULL.Soinbothcases,thesetofnonnullable*relsistheunionofthosefoundinthearms,andwepassdownthe*top_levelflagunmodified.*/foreach(l,(List*)node){result=bms_join(result,find_nonnullable_rels_walker(lfirst(l),top_level));}}elseif(IsA(node,FuncExpr)){FuncExpr*expr=(FuncExpr*)node;if(func_strict(expr->funcid))result=find_nonnullable_rels_walker((Node*)expr->args,false);}elseif(IsA(node,OpExpr)){OpExpr*expr=(OpExpr*)node;set_opfuncid(expr);if(func_strict(expr->opfuncid))result=find_nonnullable_rels_walker((Node*)expr->args,false);}elseif(IsA(node,ScalarArrayOpExpr)){ScalarArrayOpExpr*expr=(ScalarArrayOpExpr*)node;if(is_strict_saop(expr,true))result=find_nonnullable_rels_walker((Node*)expr->args,false);}elseif(IsA(node,BoolExpr)){BoolExpr*expr=(BoolExpr*)node;switch(expr->boolop){caseAND_EXPR:/*Attoplevelwecanjustrecurse(totheListcase)*/if(top_level){result=find_nonnullable_rels_walker((Node*)expr->args,top_level);break;}/**Belowtoplevel,evenifonearmproducesNULL,theresult*couldbeFALSE(hencenotNULL).However,if*all*the*armsproduceNULLthentheresultisNULL,sowecantake*theintersectionofthesetsofnonnullablerels,justas*forOR.Fallthroughtosharecode.*//*FALLTHRU*/caseOR_EXPR:/**ORisstrictifallofitsarmsare,sowecantakethe*intersectionofthesetsofnonnullablerelsforeacharm.*Thisworksforbothvaluesoftop_level.*/foreach(l,expr->args){Relidssubresult;subresult=find_nonnullable_rels_walker(lfirst(l),top_level);if(result==NULL)/*firstsubresult?*/result=subresult;elseresult=bms_int_members(result,subresult);/**Iftheintersectionisempty,wecanstoplooking.This*alsojustifiesthetestforfirst-subresultabove.*/if(bms_is_empty(result))break;}break;caseNOT_EXPR:/*NOTwillreturnnullifitsargisnull*/result=find_nonnullable_rels_walker((Node*)expr->args,false);break;default:elog(ERROR,"unrecognizedboolop:%d",(int)expr->boolop);break;}}elseif(IsA(node,RelabelType)){RelabelType*expr=(RelabelType*)node;result=find_nonnullable_rels_walker((Node*)expr->arg,top_level);}elseif(IsA(node,CoerceViaIO)){/*notclearthisisuseful,butitcan'thurt*/CoerceViaIO*expr=(CoerceViaIO*)node;result=find_nonnullable_rels_walker((Node*)expr->arg,top_level);}elseif(IsA(node,ArrayCoerceExpr)){/*ArrayCoerceExprisstrictatthearraylevel;ignoreelemexpr*/ArrayCoerceExpr*expr=(ArrayCoerceExpr*)node;result=find_nonnullable_rels_walker((Node*)expr->arg,top_level);}elseif(IsA(node,ConvertRowtypeExpr)){/*notclearthisisuseful,butitcan'thurt*/ConvertRowtypeExpr*expr=(ConvertRowtypeExpr*)node;result=find_nonnullable_rels_walker((Node*)expr->arg,top_level);}elseif(IsA(node,CollateExpr)){CollateExpr*expr=(CollateExpr*)node;result=find_nonnullable_rels_walker((Node*)expr->arg,top_level);}elseif(IsA(node,NullTest)){/*ISNOTNULLcanbeconsideredstrict,butonlyattoplevel*/NullTest*expr=(NullTest*)node;if(top_level&&expr->nulltesttype==IS_NOT_NULL&&!expr->argisrow)result=find_nonnullable_rels_walker((Node*)expr->arg,false);}elseif(IsA(node,BooleanTest)){/*BooleanteststhatrejectNULLarestrictattoplevel*/BooleanTest*expr=(BooleanTest*)node;if(top_level&&(expr->booltesttype==IS_TRUE||expr->booltesttype==IS_FALSE||expr->booltesttype==IS_NOT_UNKNOWN))result=find_nonnullable_rels_walker((Node*)expr->arg,false);}elseif(IsA(node,PlaceHolderVar)){PlaceHolderVar*phv=(PlaceHolderVar*)node;result=find_nonnullable_rels_walker((Node*)phv->phexpr,top_level);}returnresult;}三、跟踪分析
外连接->内连接
测试脚本:
select*fromt_null1aleftjoint_null2bona.c1=b.c1whereb.c1=1;
gdb跟踪:
(gdb)breduce_outer_joinsBreakpoint1at0x77faf5:fileprepjointree.c,line2484.(gdb)cContinuing.Breakpoint1,reduce_outer_joins(root=0x2eafa98)atprepjointree.c:24842484state=reduce_outer_joins_pass1((Node*)root->parse->jointree);(gdb)breduce_outer_joinsBreakpoint1at0x77faf5:fileprepjointree.c,line2484....#进入reduce_outer_joins_pass1(gdb)stepreduce_outer_joins_pass1(jtnode=0x2ea4af8)atprepjointree.c:2504...(gdb)p*f$2={type=T_FromExpr,fromlist=0x2ea4668,quals=0x2edcae8}#进入FromExpr分支(gdb)n2527sub_state=reduce_outer_joins_pass1(lfirst(l));##递归调用(gdb)stepreduce_outer_joins_pass1(jtnode=0x2dd40f0)atprepjointree.c:25042504result=(reduce_outer_joins_state*)##进入JoinExpr分支2534elseif(IsA(jtnode,JoinExpr))(gdb)2536JoinExpr*j=(JoinExpr*)jtnode;(gdb)p*j$3={type=T_JoinExpr,jointype=JOIN_LEFT,isNatural=false,larg=0x2dd49e0,rarg=0x2dd4c68,usingClause=0x0,quals=0x2edc828,alias=0x0,rtindex=3}###递归调用2543sub_state=reduce_outer_joins_pass1(j->larg);(gdb)stepreduce_outer_joins_pass1(jtnode=0x2dd49e0)atprepjointree.c:25042504result=(reduce_outer_joins_state*)###进入RangeTblRef分支2512if(IsA(jtnode,RangeTblRef))(gdb)2514intvarno=((RangeTblRef*)jtnode)->rtindex;...###JoinExpr处理完毕2549sub_state=reduce_outer_joins_pass1(j->rarg);(gdb)2551sub_state->relids);(gdb)2550result->relids=bms_add_members(result->relids,(gdb)2552result->contains_outer|=sub_state->contains_outer;(gdb)2553result->sub_states=lappend(result->sub_states,sub_state);(gdb)2558returnresult;(gdb)p*result$12={relids=0x2ea61e8,contains_outer=true,sub_states=0x2edcbc8}(gdb)p*result->relids$13={nwords=1,words=0x2ea61ec}(gdb)p*result->sub_states$14={type=T_List,length=2,head=0x2edcba8,tail=0x2edcc40}(gdb)n2559}(gdb)##回到FromExpr...2523foreach(l,f->fromlist)(gdb)p*result$18={relids=0x2edcc60,contains_outer=true,sub_states=0x2edcc98}#回到reduce_outer_joinsreduce_outer_joins(root=0x2eafa98)atprepjointree.c:24872487if(state==NULL||!state->contains_outer)(gdb)p*state$21={relids=0x2edcc60,contains_outer=true,sub_states=0x2edcc98}(gdb)p*state->relids[0]->words$30=6-->Relids,1&2,即1<<1|1<<2#进入reduce_outer_joins_pass2(gdb)stepreduce_outer_joins_pass2(jtnode=0x2ea4af8,state=0x2edcb18,root=0x2eafa98,nonnullable_rels=0x0,nonnullable_vars=0x0,forced_null_vars=0x0)atprepjointree.c:25832583if(jtnode==NULL)...#进入FromExpr分支2587elseif(IsA(jtnode,FromExpr))(gdb)2589FromExpr*f=(FromExpr*)jtnode;...#寻找FromExpr中存在过滤条件为NOTNULL的Relids(gdb)n2597pass_nonnullable_rels=find_nonnullable_rels(f->quals);(gdb)2598pass_nonnullable_rels=bms_add_members(pass_nonnullable_rels,(gdb)ppass_nonnullable_rels->words[0]$34=4--rtindex=2的Relid#寻找NOTNULL'sVars2601pass_nonnullable_vars=find_nonnullable_vars(f->quals);(gdb)2602pass_nonnullable_vars=list_concat(pass_nonnullable_vars,(gdb)2604pass_forced_null_vars=find_forced_null_vars(f->quals);(gdb)p*pass_nonnullable_vars$35={type=T_List,length=1,head=0x2edcce0,tail=0x2edcce0}(gdb)p*(Node*)pass_nonnullable_vars->head->data.ptr_value$36={type=T_Var}(gdb)p*(Var*)pass_nonnullable_vars->head->data.ptr_value$37={xpr={type=T_Var},varno=2,varattno=1,vartype=23,vartypmod=-1,varcollid=0,varlevelsup=0,varnoold=2,varoattno=1,location=65}--rtindex=2的RTE,属性编号为1的字段##递归调用reduce_outer_joins_pass2(gdb)2614reduce_outer_joins_pass2(lfirst(l),sub_state,root,(gdb)stepreduce_outer_joins_pass2(jtnode=0x2dd40f0,state=0x2edcb48,root=0x2eafa98,nonnullable_rels=0x2edccc8,nonnullable_vars=0x2edcd00,forced_null_vars=0x0)atprepjointree.c:25832583if(jtnode==NULL)##进入JoinExpr分支2622elseif(IsA(jtnode,JoinExpr))(gdb)2624JoinExpr*j=(JoinExpr*)jtnode;...(gdb)p*right_state->relids[0]->words$44=4-->2号RTE(gdb)p*left_state->relids[0]->words$45=2-->1号RTE(gdb)prtindex$46=3-->Jointree整体作为3号RTE存在...2633switch(jointype)(gdb)2638if(bms_overlap(nonnullable_rels,right_state->relids))(gdb)p*nonnullable_rels->words$49=4-->2号RTE(gdb)pright_state->relids->words[0]$50=4-->2号RTE##转换为内连接(gdb)n2639jointype=JOIN_INNER;...##修改RTE的连接类型(gdb)2724if(rtindex&&jointype!=j->jointype)(gdb)2726RangeTblEntry*rte=rt_fetch(rtindex,root->parse->rtable);(gdb)2730rte->jointype=jointype;(gdb)p*rte$54={type=T_RangeTblEntry,rtekind=RTE_JOIN,relid=0,relkind=0'\000',tablesample=0x0,subquery=0x0,security_barrier=false,jointype=JOIN_LEFT,joinaliasvars=0x0,functions=0x0,funcordinality=false,tablefunc=0x0,values_lists=0x0,ctename=0x0,ctelevelsup=0,self_reference=false,coltypes=0x0,coltypmods=0x0,colcollations=0x0,enrname=0x0,enrtuples=0,alias=0x0,eref=0x2ea4498,lateral=false,inh=false,inFromCl=true,requiredPerms=2,checkAsUser=0,selectedCols=0x0,insertedCols=0x0,updatedCols=0x0,securityQuals=0x0}(gdb)n2732j->jointype=jointype;...#回到上层的reduce_outer_joins_pass2(gdb)reduce_outer_joins_pass2(jtnode=0x2ea4af8,state=0x2edcb18,root=0x2eafa98,nonnullable_rels=0x0,nonnullable_vars=0x0,forced_null_vars=0x0)atprepjointree.c:26092609forboth(l,f->fromlist,s,state->sub_states)#回到主函数reduce_outer_joins2844}(gdb)reduce_outer_joins(root=0x2eafa98)atprepjointree.c:24922492}(gdb)#DONE!
外连接->反连接
测试脚本:
select*fromt_null1aleftjoint_null2bona.c1=b.c1whereb.c1isnull;
gdb跟踪,与转换为内连接不同的地方在于reduce_outer_joins_pass2函数中find_forced_null_vars在这里是可以找到相应的Vars的:
(gdb)bprepjointree.c:2702Breakpoint3at0x78023c:fileprepjointree.c,line2702.(gdb)cContinuing.Breakpoint3,reduce_outer_joins_pass2(jtnode=0x2dd40f0,state=0x2edc9b8,root=0x2eafa98,nonnullable_rels=0x0,nonnullable_vars=0x0,forced_null_vars=0x2edcb70)atprepjointree.c:27032703if(jointype==JOIN_LEFT)#尝试转换为内连接,但不成功,仍为左连接2707local_nonnullable_vars=find_nonnullable_vars(j->quals);(gdb)2708computed_local_nonnullable_vars=true;(gdb)p*local_nonnullable_vars$56={type=T_List,length=2,head=0x2edcba0,tail=0x2edcbf0}(gdb)p*(Var*)local_nonnullable_vars->head->data.ptr_value$57={xpr={type=T_Var},varno=1,varattno=1,vartype=23,vartypmod=-1,varcollid=0,varlevelsup=0,varnoold=1,varoattno=1,location=47}(gdb)p*(Var*)local_nonnullable_vars->head->next->data.ptr_value$58={xpr={type=T_Var},varno=2,varattno=1,vartype=23,vartypmod=-1,varcollid=0,varlevelsup=0,varnoold=2,varoattno=1,location=54}(gdb)p*(Var*)forced_null_vars->head->data.ptr_value$61={xpr={type=T_Var},varno=2,varattno=1,vartype=23,vartypmod=-1,varcollid=0,varlevelsup=0,varnoold=2,varoattno=1,location=65}(gdb)p*(Var*)overlap->head->data.ptr_value$63={xpr={type=T_Var},varno=2,varattno=1,vartype=23,vartypmod=-1,varcollid=0,varlevelsup=0,varnoold=2,varoattno=1,location=54}...#转换为反连接2717if(overlap!=NIL&&(gdb)2720jointype=JOIN_ANTI;(gdb)cContinuing.
“PostgreSQL查询优化中对消除外连接的处理过程是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。