千家信息网

PostgreSQL查询优化中怎么上拉子链接

发表于:2025-02-04 作者:千家信息网编辑
千家信息网最后更新 2025年02月04日,本篇内容主要讲解"PostgreSQL查询优化中怎么上拉子链接",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"PostgreSQL查询优化中怎么上拉子链接"
千家信息网最后更新 2025年02月04日PostgreSQL查询优化中怎么上拉子链接

本篇内容主要讲解"PostgreSQL查询优化中怎么上拉子链接",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"PostgreSQL查询优化中怎么上拉子链接"吧!

查询树

convert_EXISTS_sublink_to_join函数源码:

 /*  * convert_EXISTS_sublink_to_join: try to convert an EXISTS SubLink to a join  *  * The API of this function is identical to convert_ANY_sublink_to_join's,  * except that we also support the case where the caller has found NOT EXISTS,  * so we need an additional input parameter "under_not".  * 逻辑与ANY一致,为了支持NOT EXISTS,多加了一个参数under_not  */   JoinExpr * convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,                                bool under_not, Relids available_rels) {     JoinExpr   *result;//返回结果     Query      *parse = root->parse;//查询树     Query      *subselect = (Query *) sublink->subselect;//子查询     Node       *whereClause;//where语句     int         rtoffset;//     int         varno;     Relids      clause_varnos;     Relids      upper_varnos;      Assert(sublink->subLinkType == EXISTS_SUBLINK);      /*      * Can't flatten if it contains WITH.  (We could arrange to pull up the      * WITH into the parent query's cteList, but that risks changing the      * semantics, since a WITH ought to be executed once per associated query      * call.)  Note that convert_ANY_sublink_to_join doesn't have to reject      * this case, since it just produces a subquery RTE that doesn't have to      * get flattened into the parent query.      */     if (subselect->cteList)//存在With子句,返回         return NULL;      /*      * Copy the subquery so we can modify it safely (see comments in      * make_subplan).      */     subselect = copyObject(subselect);      /*      * See if the subquery can be simplified based on the knowledge that it's      * being used in EXISTS().  If we aren't able to get rid of its      * targetlist, we have to fail, because the pullup operation leaves us      * with noplace to evaluate the targetlist.      */     //能否handle targetList?不行,则退出     //如果含有集合操作/聚合操作/Having子句等,子链接不能提升     if (!simplify_EXISTS_query(root, subselect))         return NULL;      /*      * The subquery must have a nonempty jointree, else we won't have a join.      */     if (subselect->jointree->fromlist == NIL)//子查询没有查询主体,退出         return NULL;      /*      * Separate out the WHERE clause.  (We could theoretically also remove      * top-level plain JOIN/ON clauses, but it's probably not worth the      * trouble.)      */     whereClause = subselect->jointree->quals;//子查询条件语句单独保存     subselect->jointree->quals = NULL;//子查询的条件语句设置为NULL      /*      * The rest of the sub-select must not refer to any Vars of the parent      * query.  (Vars of higher levels should be okay, though.)      */     if (contain_vars_of_level((Node *) subselect, 1))//去掉条件语句后,如仍依赖父查询的Vars,退出         return NULL;      /*      * On the other hand, the WHERE clause must contain some Vars of the      * parent query, else it's not gonna be a join.      */     if (!contain_vars_of_level(whereClause, 1))//条件语句必须含有父查询的Vars,否则不构成连接,退出         return NULL;      /*      * We don't risk optimizing if the WHERE clause is volatile, either.      */     if (contain_volatile_functions(whereClause))//条件语句存在易变函数(如随机函数等)         return NULL;      /*      * Prepare to pull up the sub-select into top range table.      *      * We rely here on the assumption that the outer query has no references      * to the inner (necessarily true). Therefore this is a lot easier than      * what pull_up_subqueries has to go through.      *      * In fact, it's even easier than what convert_ANY_sublink_to_join has to      * do.  The machinations of simplify_EXISTS_query ensured that there is      * nothing interesting in the subquery except an rtable and jointree, and      * even the jointree FromExpr no longer has quals.  So we can just append      * the rtable to our own and use the FromExpr in our jointree. But first,      * adjust all level-zero varnos in the subquery to account for the rtable      * merger.      */     rtoffset = list_length(parse->rtable);//获取rtable的长度(新RTE插入的偏移)     //调整子查询中varno为0(指向rtable)的Vars,varno调整为父查询rtable的index     //(详见依赖函数解析)     OffsetVarNodes((Node *) subselect, rtoffset, 0);     OffsetVarNodes(whereClause, rtoffset, 0);      /*      * Upper-level vars in subquery will now be one level closer to their      * parent than before; in particular, anything that had been level 1      * becomes level zero.      */     //子查询中与父查询相关的Vars,varlevelsup需要从Leve 1变为Level 0     //在子查询中,这些Vars的varlevelsup为1,表示依赖于父查询(上一层)的Vars,提升后不存在此依赖,需改为0     IncrementVarSublevelsUp((Node *) subselect, -1, 1);     IncrementVarSublevelsUp(whereClause, -1, 1);      /*      * Now that the WHERE clause is adjusted to match the parent query      * environment, we can easily identify all the level-zero rels it uses.      * The ones <= rtoffset belong to the upper query; the ones > rtoffset do      * not.      */     clause_varnos = pull_varnos(whereClause);     upper_varnos = NULL;     while ((varno = bms_first_member(clause_varnos)) >= 0)     {         if (varno <= rtoffset)             upper_varnos = bms_add_member(upper_varnos, varno);     }     bms_free(clause_varnos);     Assert(!bms_is_empty(upper_varnos));      /*      * Now that we've got the set of upper-level varnos, we can make the last      * check: only available_rels can be referenced.      */     if (!bms_is_subset(upper_varnos, available_rels))         return NULL;      /* Now we can attach the modified subquery rtable to the parent */     //把子查询的rtable拼接到父查询的rtable中     parse->rtable = list_concat(parse->rtable, subselect->rtable);      //构造JoinExpr     /*      * And finally, build the JoinExpr node.      */     result = makeNode(JoinExpr);     result->jointype = under_not ? JOIN_ANTI : JOIN_SEMI;     result->isNatural = false;     result->larg = NULL;        /* caller must fill this in */     /* flatten out the FromExpr node if it's useless */     if (list_length(subselect->jointree->fromlist) == 1)         result->rarg = (Node *) linitial(subselect->jointree->fromlist);     else         result->rarg = (Node *) subselect->jointree;     result->usingClause = NIL;     result->quals = whereClause;     result->alias = NULL;     result->rtindex = 0;        /* we don't need an RTE for it */      return result; }

二、基础信息

相关数据结构
1、Var

 /*  * Var - expression node representing a variable (ie, a table column)  *  * Note: during parsing/planning, varnoold/varoattno are always just copies  * of varno/varattno.  At the tail end of planning, Var nodes appearing in  * upper-level plan nodes are reassigned to point to the outputs of their  * subplans; for example, in a join node varno becomes INNER_VAR or OUTER_VAR  * and varattno becomes the index of the proper element of that subplan's  * target list.  Similarly, INDEX_VAR is used to identify Vars that reference  * an index column rather than a heap column.  (In ForeignScan and CustomScan  * plan nodes, INDEX_VAR is abused to signify references to columns of a  * custom scan tuple type.)  In all these cases, varnoold/varoattno hold the  * original values.  The code doesn't really need varnoold/varoattno, but they  * are very useful for debugging and interpreting completed plans, so we keep  * them around.  */ #define    INNER_VAR        65000   /* reference to inner subplan */ #define    OUTER_VAR        65001   /* reference to outer subplan */ #define    INDEX_VAR        65002   /* reference to index column */  #define IS_SPECIAL_VARNO(varno)     ((varno) >= INNER_VAR)  /* Symbols for the indexes of the special RTE entries in rules */ #define    PRS2_OLD_VARNO           1 #define    PRS2_NEW_VARNO           2  typedef struct Var {     Expr        xpr;     Index       varno;          /* index of this var's relation in the range                                  * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */     AttrNumber  varattno;       /* attribute number of this var, or zero for                                  * all attrs ("whole-row Var") */     Oid         vartype;        /* pg_type OID for the type of this var */     int32       vartypmod;      /* pg_attribute typmod value */     Oid         varcollid;      /* OID of collation, or InvalidOid if none */     Index       varlevelsup;    /* for subquery variables referencing outer                                  * relations; 0 in a normal var, >0 means N                                  * levels up */     Index       varnoold;       /* original value of varno, for debugging */     AttrNumber  varoattno;      /* original value of varattno */     int         location;       /* token location, or -1 if unknown */ } Var;

XX_one_pos

 /*  * Lookup tables to avoid need for bit-by-bit groveling  *  * rightmost_one_pos[x] gives the bit number (0-7) of the rightmost one bit  * in a nonzero byte value x.  The entry for x=0 is never used.  *  * leftmost_one_pos[x] gives the bit number (0-7) of the leftmost one bit in a  * nonzero byte value x.  The entry for x=0 is never used.  *  * number_of_ones[x] gives the number of one-bits (0-8) in a byte value x.  *  * We could make these tables larger and reduce the number of iterations  * in the functions that use them, but bytewise shifts and masks are  * especially fast on many machines, so working a byte at a time seems best.  */  static const uint8 rightmost_one_pos[256] = {     0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 };  static const uint8 leftmost_one_pos[256] = {     0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,     4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,     5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,     5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 };  static const uint8 number_of_ones[256] = {     0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,     1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,     1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,     2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,     1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,     2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,     2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,     3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,     1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,     2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,     2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,     3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,     2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,     3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,     3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,     4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 };

依赖的函数
simplify_EXISTS_query

 /*  * simplify_EXISTS_query: remove any useless stuff in an EXISTS's subquery  *  * The only thing that matters about an EXISTS query is whether it returns  * zero or more than zero rows.  Therefore, we can remove certain SQL features  * that won't affect that.  The only part that is really likely to matter in  * typical usage is simplifying the targetlist: it's a common habit to write  * "SELECT * FROM" even though there is no need to evaluate any columns.  *  * Note: by suppressing the targetlist we could cause an observable behavioral  * change, namely that any errors that might occur in evaluating the tlist  * won't occur, nor will other side-effects of volatile functions.  This seems  * unlikely to bother anyone in practice.  *  * Returns true if was able to discard the targetlist, else false.  */ static bool simplify_EXISTS_query(PlannerInfo *root, Query *query) {     /*      * We don't try to simplify at all if the query uses set operations,      * aggregates, grouping sets, SRFs, modifying CTEs, HAVING, OFFSET, or FOR      * UPDATE/SHARE; none of these seem likely in normal usage and their      * possible effects are complex.  (Note: we could ignore an "OFFSET 0"      * clause, but that traditionally is used as an optimization fence, so we      * don't.)      */     if (query->commandType != CMD_SELECT ||         query->setOperations ||         query->hasAggs ||         query->groupingSets ||         query->hasWindowFuncs ||         query->hasTargetSRFs ||         query->hasModifyingCTE ||         query->havingQual ||         query->limitOffset ||         query->rowMarks)         return false;      /*      * LIMIT with a constant positive (or NULL) value doesn't affect the      * semantics of EXISTS, so let's ignore such clauses.  This is worth doing      * because people accustomed to certain other DBMSes may be in the habit      * of writing EXISTS(SELECT ... LIMIT 1) as an optimization.  If there's a      * LIMIT with anything else as argument, though, we can't simplify.      */     if (query->limitCount)     {         /*          * The LIMIT clause has not yet been through eval_const_expressions,          * so we have to apply that here.  It might seem like this is a waste          * of cycles, since the only case plausibly worth worrying about is          * "LIMIT 1" ... but what we'll actually see is "LIMIT int8(1::int4)",          * so we have to fold constants or we're not going to recognize it.          */         Node       *node = eval_const_expressions(root, query->limitCount);         Const      *limit;          /* Might as well update the query if we simplified the clause. */         query->limitCount = node;          if (!IsA(node, Const))             return false;          limit = (Const *) node;         Assert(limit->consttype == INT8OID);         if (!limit->constisnull && DatumGetInt64(limit->constvalue) <= 0)             return false;          /* Whether or not the targetlist is safe, we can drop the LIMIT. */         query->limitCount = NULL;     }      /*      * Otherwise, we can throw away the targetlist, as well as any GROUP,      * WINDOW, DISTINCT, and ORDER BY clauses; none of those clauses will      * change a nonzero-rows result to zero rows or vice versa.  (Furthermore,      * since our parsetree representation of these clauses depends on the      * targetlist, we'd better throw them away if we drop the targetlist.)      */     query->targetList = NIL;     query->groupClause = NIL;     query->windowClause = NIL;     query->distinctClause = NIL;     query->sortClause = NIL;     query->hasDistinctOn = false;      return true; }

OffsetVarNodes

 void OffsetVarNodes(Node *node, int offset, int sublevels_up) {     OffsetVarNodes_context context;//上下文      context.offset = offset;//保存传入的offset     context.sublevels_up = sublevels_up;      /*      * Must be prepared to start with a Query or a bare expression tree; if      * it's a Query, go straight to query_tree_walker to make sure that      * sublevels_up doesn't get incremented prematurely.      */     if (node && IsA(node, Query))     {         Query      *qry = (Query *) node;          /*          * If we are starting at a Query, and sublevels_up is zero, then we          * must also fix rangetable indexes in the Query itself --- namely          * resultRelation, exclRelIndex and rowMarks entries.  sublevels_up          * cannot be zero when recursing into a subquery, so there's no need          * to have the same logic inside OffsetVarNodes_walker.          */         if (sublevels_up == 0)         {             ListCell   *l;              if (qry->resultRelation)                 qry->resultRelation += offset;              if (qry->onConflict && qry->onConflict->exclRelIndex)                 qry->onConflict->exclRelIndex += offset;              foreach(l, qry->rowMarks)             {                 RowMarkClause *rc = (RowMarkClause *) lfirst(l);                  rc->rti += offset;             }         }         query_tree_walker(qry, OffsetVarNodes_walker,                           (void *) &context, 0);     }     else         OffsetVarNodes_walker(node, &context); }

IncrementVarSublevelsUp

 void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,                         int min_sublevels_up) {     IncrementVarSublevelsUp_context context;      context.delta_sublevels_up = delta_sublevels_up;     context.min_sublevels_up = min_sublevels_up;      /*      * Must be prepared to start with a Query or a bare expression tree; if      * it's a Query, we don't want to increment sublevels_up.      */     query_or_expression_tree_walker(node,                                     IncrementVarSublevelsUp_walker,                                     (void *) &context,                                     QTW_EXAMINE_RTES); }

IncrementVarSublevelsUp

 /*  * IncrementVarSublevelsUp - adjust Var nodes when pushing them down in tree  *  * Find all Var nodes in the given tree having varlevelsup >= min_sublevels_up,  * and add delta_sublevels_up to their varlevelsup value.  This is needed when  * an expression that's correct for some nesting level is inserted into a  * subquery.  Ordinarily the initial call has min_sublevels_up == 0 so that  * all Vars are affected.  The point of min_sublevels_up is that we can  * increment it when we recurse into a sublink, so that local variables in  * that sublink are not affected, only outer references to vars that belong  * to the expression's original query level or parents thereof.  *  * Likewise for other nodes containing levelsup fields, such as Aggref.  *  * NOTE: although this has the form of a walker, we cheat and modify the  * Var nodes in-place.  The given expression tree should have been copied  * earlier to ensure that no unwanted side-effects occur!  */  typedef struct {     int         delta_sublevels_up;     int         min_sublevels_up; } IncrementVarSublevelsUp_context;  static bool IncrementVarSublevelsUp_walker(Node *node,                                IncrementVarSublevelsUp_context *context) {     if (node == NULL)         return false;     if (IsA(node, Var))     {         Var        *var = (Var *) node;          if (var->varlevelsup >= context->min_sublevels_up)             var->varlevelsup += context->delta_sublevels_up;         return false;           /* done here */     }     if (IsA(node, CurrentOfExpr))     {         /* this should not happen */         if (context->min_sublevels_up == 0)             elog(ERROR, "cannot push down CurrentOfExpr");         return false;     }     if (IsA(node, Aggref))     {         Aggref     *agg = (Aggref *) node;          if (agg->agglevelsup >= context->min_sublevels_up)             agg->agglevelsup += context->delta_sublevels_up;         /* fall through to recurse into argument */     }     if (IsA(node, GroupingFunc))     {         GroupingFunc *grp = (GroupingFunc *) node;          if (grp->agglevelsup >= context->min_sublevels_up)             grp->agglevelsup += context->delta_sublevels_up;         /* fall through to recurse into argument */     }     if (IsA(node, PlaceHolderVar))     {         PlaceHolderVar *phv = (PlaceHolderVar *) node;          if (phv->phlevelsup >= context->min_sublevels_up)             phv->phlevelsup += context->delta_sublevels_up;         /* fall through to recurse into argument */     }     if (IsA(node, RangeTblEntry))     {         RangeTblEntry *rte = (RangeTblEntry *) node;          if (rte->rtekind == RTE_CTE)         {             if (rte->ctelevelsup >= context->min_sublevels_up)                 rte->ctelevelsup += context->delta_sublevels_up;         }         return false;           /* allow range_table_walker to continue */     }     if (IsA(node, Query))     {         /* Recurse into subselects */         bool        result;          context->min_sublevels_up++;         result = query_tree_walker((Query *) node,                                    IncrementVarSublevelsUp_walker,                                    (void *) context,                                    QTW_EXAMINE_RTES);         context->min_sublevels_up--;         return result;     }     return expression_tree_walker(node, IncrementVarSublevelsUp_walker,                                   (void *) context); }

pull_varnos

/*  * pull_varnos  *      Create a set of all the distinct varnos present in a parsetree.  *      Only varnos that reference level-zero rtable entries are considered.  *  * NOTE: this is used on not-yet-planned expressions.  It may therefore find  * bare SubLinks, and if so it needs to recurse into them to look for uplevel  * references to the desired rtable level!  But when we find a completed  * SubPlan, we only need to look at the parameters passed to the subplan.  */ Relids pull_varnos(Node *node) {     pull_varnos_context context;      context.varnos = NULL;     context.sublevels_up = 0;      /*      * Must be prepared to start with a Query or a bare expression tree; if      * it's a Query, we don't want to increment sublevels_up.      */     query_or_expression_tree_walker(node,                                     pull_varnos_walker,                                     (void *) &context,                                     0);      return context.varnos; }

contain_vars_of_level

/*  * contain_vars_of_level  *    Recursively scan a clause to discover whether it contains any Var nodes  *    of the specified query level.  *  *    Returns true if any such Var found.  *  * Will recurse into sublinks.  Also, may be invoked directly on a Query.  */ bool contain_vars_of_level(Node *node, int levelsup) {     int         sublevels_up = levelsup;      return query_or_expression_tree_walker(node,                                            contain_vars_of_level_walker,                                            (void *) &sublevels_up,                                            0); }

query_tree_walker

  /*  * query_tree_walker --- initiate a walk of a Query's expressions  *  * This routine exists just to reduce the number of places that need to know  * where all the expression subtrees of a Query are.  Note it can be used  * for starting a walk at top level of a Query regardless of whether the  * walker intends to descend into subqueries.  It is also useful for  * descending into subqueries within a walker.  *  * Some callers want to suppress visitation of certain items in the sub-Query,  * typically because they need to process them specially, or don't actually  * want to recurse into subqueries.  This is supported by the flags argument,  * which is the bitwise OR of flag values to suppress visitation of  * indicated items.  (More flag bits may be added as needed.)  */ bool query_tree_walker(Query *query,                   bool (*walker) (),                   void *context,                   int flags) {     Assert(query != NULL && IsA(query, Query));      if (walker((Node *) query->targetList, context))         return true;     if (walker((Node *) query->withCheckOptions, context))         return true;     if (walker((Node *) query->onConflict, context))         return true;     if (walker((Node *) query->returningList, context))         return true;     if (walker((Node *) query->jointree, context))         return true;     if (walker(query->setOperations, context))         return true;     if (walker(query->havingQual, context))         return true;     if (walker(query->limitOffset, context))         return true;     if (walker(query->limitCount, context))         return true;     if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))     {         if (walker((Node *) query->cteList, context))             return true;     }     if (!(flags & QTW_IGNORE_RANGE_TABLE))     {         if (range_table_walker(query->rtable, walker, context, flags))             return true;     }     return false; }

OffsetVarNodes_walker

 /*  * OffsetVarNodes - adjust Vars when appending one query's RT to another  *  * Find all Var nodes in the given tree with varlevelsup == sublevels_up,  * and increment their varno fields (rangetable indexes) by 'offset'.  * The varnoold fields are adjusted similarly.  Also, adjust other nodes  * that contain rangetable indexes, such as RangeTblRef and JoinExpr.  *  * NOTE: although this has the form of a walker, we cheat and modify the  * nodes in-place.  The given expression tree should have been copied  * earlier to ensure that no unwanted side-effects occur!  */  typedef struct {     int         offset;     int         sublevels_up; } OffsetVarNodes_context;  static bool OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context) {     if (node == NULL)         return false;     if (IsA(node, Var))     {         Var        *var = (Var *) node;          if (var->varlevelsup == context->sublevels_up)         {             var->varno += context->offset;             var->varnoold += context->offset;         }         return false;     }     if (IsA(node, CurrentOfExpr))     {         CurrentOfExpr *cexpr = (CurrentOfExpr *) node;          if (context->sublevels_up == 0)             cexpr->cvarno += context->offset;         return false;     }     if (IsA(node, RangeTblRef))     {         RangeTblRef *rtr = (RangeTblRef *) node;          if (context->sublevels_up == 0)             rtr->rtindex += context->offset;         /* the subquery itself is visited separately */         return false;     }     if (IsA(node, JoinExpr))     {         JoinExpr   *j = (JoinExpr *) node;          if (j->rtindex && context->sublevels_up == 0)             j->rtindex += context->offset;         /* fall through to examine children */     }     if (IsA(node, PlaceHolderVar))     {         PlaceHolderVar *phv = (PlaceHolderVar *) node;          if (phv->phlevelsup == context->sublevels_up)         {             phv->phrels = offset_relid_set(phv->phrels,                                            context->offset);         }         /* fall through to examine children */     }     if (IsA(node, AppendRelInfo))     {         AppendRelInfo *appinfo = (AppendRelInfo *) node;          if (context->sublevels_up == 0)         {             appinfo->parent_relid += context->offset;             appinfo->child_relid += context->offset;         }         /* fall through to examine children */     }     /* Shouldn't need to handle other planner auxiliary nodes here */     Assert(!IsA(node, PlanRowMark));     Assert(!IsA(node, SpecialJoinInfo));     Assert(!IsA(node, PlaceHolderInfo));     Assert(!IsA(node, MinMaxAggInfo));      if (IsA(node, Query))     {         /* Recurse into subselects */         bool        result;          context->sublevels_up++;         result = query_tree_walker((Query *) node, OffsetVarNodes_walker,                                    (void *) context, 0);         context->sublevels_up--;         return result;     }     return expression_tree_walker(node, OffsetVarNodes_walker,                                   (void *) context); }

query_or_expression_tree_walker

 /*  * query_or_expression_tree_walker --- hybrid form  *  * This routine will invoke query_tree_walker if called on a Query node,  * else will invoke the walker directly.  This is a useful way of starting  * the recursion when the walker's normal change of state is not appropriate  * for the outermost Query node.  */ bool query_or_expression_tree_walker(Node *node,                                 bool (*walker) (),                                 void *context,                                 int flags) {     if (node && IsA(node, Query))         return query_tree_walker((Query *) node,                                  walker,                                  context,                                  flags);     else         return walker(node, context); }

pull_varnos_walker

 static bool pull_varnos_walker(Node *node, pull_varnos_context *context) {     if (node == NULL)         return false;     if (IsA(node, Var))     {         Var        *var = (Var *) node;          if (var->varlevelsup == context->sublevels_up)             context->varnos = bms_add_member(context->varnos, var->varno);         return false;     }     if (IsA(node, CurrentOfExpr))     {         CurrentOfExpr *cexpr = (CurrentOfExpr *) node;          if (context->sublevels_up == 0)             context->varnos = bms_add_member(context->varnos, cexpr->cvarno);         return false;     }     if (IsA(node, PlaceHolderVar))     {         /*          * A PlaceHolderVar acts as a variable of its syntactic scope, or          * lower than that if it references only a subset of the rels in its          * syntactic scope.  It might also contain lateral references, but we          * should ignore such references when computing the set of varnos in          * an expression tree.  Also, if the PHV contains no variables within          * its syntactic scope, it will be forced to be evaluated exactly at          * the syntactic scope, so take that as the relid set.          */         PlaceHolderVar *phv = (PlaceHolderVar *) node;         pull_varnos_context subcontext;          subcontext.varnos = NULL;         subcontext.sublevels_up = context->sublevels_up;         (void) pull_varnos_walker((Node *) phv->phexpr, &subcontext);         if (phv->phlevelsup == context->sublevels_up)         {             subcontext.varnos = bms_int_members(subcontext.varnos,                                                 phv->phrels);             if (bms_is_empty(subcontext.varnos))                 context->varnos = bms_add_members(context->varnos,                                                   phv->phrels);         }         context->varnos = bms_join(context->varnos, subcontext.varnos);         return false;     }     if (IsA(node, Query))     {         /* Recurse into RTE subquery or not-yet-planned sublink subquery */         bool        result;          context->sublevels_up++;         result = query_tree_walker((Query *) node, pull_varnos_walker,                                    (void *) context, 0);         context->sublevels_up--;         return result;     }     return expression_tree_walker(node, pull_varnos_walker,                                   (void *) context); }

contain_vars_of_level_walker

 static bool contain_vars_of_level_walker(Node *node, int *sublevels_up) {     if (node == NULL)         return false;     if (IsA(node, Var))     {         if (((Var *) node)->varlevelsup == *sublevels_up)             return true;        /* abort tree traversal and return true */         return false;     }     if (IsA(node, CurrentOfExpr))     {         if (*sublevels_up == 0)             return true;         return false;     }     if (IsA(node, PlaceHolderVar))     {         if (((PlaceHolderVar *) node)->phlevelsup == *sublevels_up)             return true;        /* abort the tree traversal and return true */         /* else fall through to check the contained expr */     }     if (IsA(node, Query))     {         /* Recurse into subselects */         bool        result;          (*sublevels_up)++;         result = query_tree_walker((Query *) node,                                    contain_vars_of_level_walker,                                    (void *) sublevels_up,                                    0);         (*sublevels_up)--;         return result;     }     return expression_tree_walker(node,                                   contain_vars_of_level_walker,                                   (void *) sublevels_up); }

expression_tree_walker

 /*  * Standard expression-tree walking support  *  * We used to have near-duplicate code in many different routines that  * understood how to recurse through an expression node tree.  That was  * a pain to maintain, and we frequently had bugs due to some particular  * routine neglecting to support a particular node type.  In most cases,  * these routines only actually care about certain node types, and don't  * care about other types except insofar as they have to recurse through  * non-primitive node types.  Therefore, we now provide generic tree-walking  * logic to consolidate the redundant "boilerplate" code.  There are  * two versions: expression_tree_walker() and expression_tree_mutator().  */  /*  * expression_tree_walker() is designed to support routines that traverse  * a tree in a read-only fashion (although it will also work for routines  * that modify nodes in-place but never add/delete/replace nodes).  * A walker routine should look like this:  *  * bool my_walker (Node *node, my_struct *context)  * {  *      if (node == NULL)  *          return false;  *      // check for nodes that special work is required for, eg:  *      if (IsA(node, Var))  *      {  *          ... do special actions for Var nodes  *      }  *      else if (IsA(node, ...))  *      {  *          ... do special actions for other node types  *      }  *      // for any node type not specially processed, do:  *      return expression_tree_walker(node, my_walker, (void *) context);  * }  *  * The "context" argument points to a struct that holds whatever context  * information the walker routine needs --- it can be used to return data  * gathered by the walker, too.  This argument is not touched by  * expression_tree_walker, but it is passed down to recursive sub-invocations  * of my_walker.  The tree walk is started from a setup routine that  * fills in the appropriate context struct, calls my_walker with the top-level  * node of the tree, and then examines the results.  *  * The walker routine should return "false" to continue the tree walk, or  * "true" to abort the walk and immediately return "true" to the top-level  * caller.  This can be used to short-circuit the traversal if the walker  * has found what it came for.  "false" is returned to the top-level caller  * iff no invocation of the walker returned "true".  *  * The node types handled by expression_tree_walker include all those  * normally found in target lists and qualifier clauses during the planning  * stage.  In particular, it handles List nodes since a cnf-ified qual clause  * will have List structure at the top level, and it handles TargetEntry nodes  * so that a scan of a target list can be handled without additional code.  * Also, RangeTblRef, FromExpr, JoinExpr, and SetOperationStmt nodes are  * handled, so that query jointrees and setOperation trees can be processed  * without additional code.  *  * expression_tree_walker will handle SubLink nodes by recursing normally  * into the "testexpr" subtree (which is an expression belonging to the outer  * plan).  It will also call the walker on the sub-Query node; however, when  * expression_tree_walker itself is called on a Query node, it does nothing  * and returns "false".  The net effect is that unless the walker does  * something special at a Query node, sub-selects will not be visited during  * an expression tree walk. This is exactly the behavior wanted in many cases  * --- and for those walkers that do want to recurse into sub-selects, special  * behavior is typically needed anyway at the entry to a sub-select (such as  * incrementing a depth counter). A walker that wants to examine sub-selects  * should include code along the lines of:  *  *      if (IsA(node, Query))  *      {  *          adjust context for subquery;  *          result = query_tree_walker((Query *) node, my_walker, context,  *                                     0); // adjust flags as needed  *          restore context if needed;  *          return result;  *      }  *  * query_tree_walker is a convenience routine (see below) that calls the  * walker on all the expression subtrees of the given Query node.  *  * expression_tree_walker will handle SubPlan nodes by recursing normally  * into the "testexpr" and the "args" list (which are expressions belonging to  * the outer plan).  It will not touch the completed subplan, however.  Since  * there is no link to the original Query, it is not possible to recurse into  * subselects of an already-planned expression tree.  This is OK for current  * uses, but may need to be revisited in future.  */  bool expression_tree_walker(Node *node,                        bool (*walker) (),                        void *context) {     ListCell   *temp;      /*      * The walker has already visited the current node, and so we need only      * recurse into any sub-nodes it has.      *      * We assume that the walker is not interested in List nodes per se, so      * when we expect a List we just recurse directly to self without      * bothering to call the walker.      */     if (node == NULL)         return false;      /* Guard against stack overflow due to overly complex expressions */     check_stack_depth();      switch (nodeTag(node))     {         case T_Var:         case T_Const:         case T_Param:         case T_CaseTestExpr:         case T_SQLValueFunction:         case T_CoerceToDomainValue:         case T_SetToDefault:         case T_CurrentOfExpr:         case T_NextValueExpr:         case T_RangeTblRef:         case T_SortGroupClause:             /* primitive node types with no expression subnodes */             break;         case T_WithCheckOption:             return walker(((WithCheckOption *) node)->qual, context);         case T_Aggref:             {                 Aggref     *expr = (Aggref *) node;                  /* recurse directly on List */                 if (expression_tree_walker((Node *) expr->aggdirectargs,                                            walker, context))                     return true;                 if (expression_tree_walker((Node *) expr->args,                                            walker, context))                     return true;                 if (expression_tree_walker((Node *) expr->aggorder,                                            walker, context))                     return true;                 if (expression_tree_walker((Node *) expr->aggdistinct,                                            walker, context))                     return true;                 if (walker((Node *) expr->aggfilter, context))                     return true;             }             break;         case T_GroupingFunc:             {                 GroupingFunc *grouping = (GroupingFunc *) node;                  if (expression_tree_walker((Node *) grouping->args,                                            walker, context))                     return true;             }             break;         case T_WindowFunc:             {                 WindowFunc *expr = (WindowFunc *) node;                  /* recurse directly on List */                 if (expression_tree_walker((Node *) expr->args,                                            walker, context))                     return true;                 if (walker((Node *) expr->aggfilter, context))                     return true;             }             break;         case T_ArrayRef:             {                 ArrayRef   *aref = (ArrayRef *) node;                  /* recurse directly for upper/lower array index lists */                 if (expression_tree_walker((Node *) aref->refupperindexpr,                                            walker, context))                     return true;                 if (expression_tree_walker((Node *) aref->reflowerindexpr,                                            walker, context))                     return true;                 /* walker must see the refexpr and refassgnexpr, however */                 if (walker(aref->refexpr, context))                     return true;                 if (walker(aref->refassgnexpr, context))                     return true;             }             break;         case T_FuncExpr:             {                 FuncExpr   *expr = (FuncExpr *) node;                  if (expression_tree_walker((Node *) expr->args,                                            walker, context))                     return true;             }             break;         case T_NamedArgExpr:             return walker(((NamedArgExpr *) node)->arg, context);         case T_OpExpr:         case T_DistinctExpr:    /* struct-equivalent to OpExpr */         case T_NullIfExpr:      /* struct-equivalent to OpExpr */             {                 OpExpr     *expr = (OpExpr *) node;                  if (expression_tree_walker((Node *) expr->args,                                            walker, context))                     return true;             }             break;         case T_ScalarArrayOpExpr:             {                 ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;                  if (expression_tree_walker((Node *) expr->args,                                            walker, context))                     return true;             }             break;         case T_BoolExpr:             {                 BoolExpr   *expr = (BoolExpr *) node;                  if (expression_tree_walker((Node *) expr->args,                                            walker, context))                     return true;             }             break;         case T_SubLink:             {                 SubLink    *sublink = (SubLink *) node;                  if (walker(sublink->testexpr, context))                     return true;                  /*                  * Also invoke the walker on the sublink's Query node, so it                  * can recurse into the sub-query if it wants to.                  */                 return walker(sublink->subselect, context);             }             break;         case T_SubPlan:             {                 SubPlan    *subplan = (SubPlan *) node;                  /* recurse into the testexpr, but not into the Plan */                 if (walker(subplan->testexpr, context))                     return true;                 /* also examine args list */                 if (expression_tree_walker((Node *) subplan->args,                                            walker, context))                     return true;             }             break;         case T_AlternativeSubPlan:             return walker(((AlternativeSubPlan *) node)->subplans, context);         case T_FieldSelect:             return walker(((FieldSelect *) node)->arg, context);         case T_FieldStore:             {                 FieldStore *fstore = (FieldStore *) node;                  if (walker(fstore->arg, context))                     return true;                 if (walker(fstore->newvals, context))                     return true;             }             break;         case T_RelabelType:             return walker(((RelabelType *) node)->arg, context);         case T_CoerceViaIO:             return walker(((CoerceViaIO *) node)->arg, context);         case T_ArrayCoerceExpr:             {                 ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;                  if (walker(acoerce->arg, context))                     return true;                 if (walker(acoerce->elemexpr, context))                     return true;             }             break;         case T_ConvertRowtypeExpr:             return walker(((ConvertRowtypeExpr *) node)->arg, context);         case T_CollateExpr:             return walker(((CollateExpr *) node)->arg, context);         case T_CaseExpr:             {                 CaseExpr   *caseexpr = (CaseExpr *) node;                  if (walker(caseexpr->arg, context))                     return true;                 /* we assume walker doesn't care about CaseWhens, either */                 foreach(temp, caseexpr->args)                 {                     CaseWhen   *when = lfirst_node(CaseWhen, temp);                      if (walker(when->expr, context))                         return true;                     if (walker(when->result, context))                         return true;                 }                 if (walker(caseexpr->defresult, context))                     return true;             }             break;         case T_ArrayExpr:             return walker(((ArrayExpr *) node)->elements, context);         case T_RowExpr:             /* Assume colnames isn't interesting */             return walker(((RowExpr *) node)->args, context);         case T_RowCompareExpr:             {                 RowCompareExpr *rcexpr = (RowCompareExpr *) node;                  if (walker(rcexpr->largs, context))                     return true;                 if (walker(rcexpr->rargs, context))                     return true;             }             break;         case T_CoalesceExpr:             return walker(((CoalesceExpr *) node)->args, context);         case T_MinMaxExpr:             return walker(((MinMaxExpr *) node)->args, context);         case T_XmlExpr:             {                 XmlExpr    *xexpr = (XmlExpr *) node;                  if (walker(xexpr->named_args, context))                     return true;                 /* we assume walker doesn't care about arg_names */                 if (walker(xexpr->args, context))                     return true;             }             break;         case T_NullTest:             return walker(((NullTest *) node)->arg, context);         case T_BooleanTest:             return walker(((BooleanTest *) node)->arg, context);         case T_CoerceToDomain:             return walker(((CoerceToDomain *) node)->arg, context);         case T_TargetEntry:             return walker(((TargetEntry *) node)->expr, context);         case T_Query:             /* Do nothing with a sub-Query, per discussion above */             break;         case T_WindowClause:             {                 WindowClause *wc = (WindowClause *) node;                  if (walker(wc->partitionClause, context))                     return true;                 if (walker(wc->orderClause, context))                     return true;                 if (walker(wc->startOffset, context))                     return true;                 if (walker(wc->endOffset, context))                     return true;             }             break;         case T_CommonTableExpr:             {                 CommonTableExpr *cte = (CommonTableExpr *) node;                  /*                  * Invoke the walker on the CTE's Query node, so it can                  * recurse into the sub-query if it wants to.                  */                 return walker(cte->ctequery, context);             }             break;         case T_List:             foreach(temp, (List *) node)             {                 if (walker((Node *) lfirst(temp), context))                     return true;             }             break;         case T_FromExpr:             {                 FromExpr   *from = (FromExpr *) node;                  if (walker(from->fromlist, context))                     return true;                 if (walker(from->quals, context))                     return true;             }             break;         case T_OnConflictExpr:             {                 OnConflictExpr *onconflict = (OnConflictExpr *) node;                  if (walker((Node *) onconflict->arbiterElems, context))                     return true;                 if (walker(onconflict->arbiterWhere, context))                     return true;                 if (walker(onconflict->onConflictSet, context))                     return true;                 if (walker(onconflict->onConflictWhere, context))                     return true;                 if (walker(onconflict->exclRelTlist, context))                     return true;             }             break;         case T_PartitionPruneStepOp:             {                 PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node;                  if (walker((Node *) opstep->exprs, context))                     return true;             }             break;         case T_PartitionPruneStepCombine:             /* no expression subnodes */             break;         case T_JoinExpr:             {                 JoinExpr   *join = (JoinExpr *) node;                  if (walker(join->larg, context))                     return true;                 if (walker(join->rarg, context))                     return true;                 if (walker(join->quals, context))                     return true;                  /*                  * alias clause, using list are deemed uninteresting.                  */             }             break;         case T_SetOperationStmt:             {                 SetOperationStmt *setop = (SetOperationStmt *) node;                  if (walker(setop->larg, context))                     return true;                 if (walker(setop->rarg, context))                     return true;                  /* groupClauses are deemed uninteresting */             }             break;         case T_PlaceHolderVar:             return walker(((PlaceHolderVar *) node)->phexpr, context);         case T_InferenceElem:             return walker(((InferenceElem *) node)->expr, context);         case T_AppendRelInfo:             {                 AppendRelInfo *appinfo = (AppendRelInfo *) node;                  if (expression_tree_walker((Node *) appinfo->translated_vars,                                            walker, context))                     return true;             }             break;         case T_PlaceHolderInfo:             return walker(((PlaceHolderInfo *) node)->ph_var, context);         case T_RangeTblFunction:             return walker(((RangeTblFunction *) node)->funcexpr, context);         case T_TableSampleClause:             {                 TableSampleClause *tsc = (TableSampleClause *) node;                  if (expression_tree_walker((Node *) tsc->args,                                            walker, context))                     return true;                 if (walker((Node *) tsc->repeatable, context))                     return true;             }             break;         case T_TableFunc:             {                 TableFunc  *tf = (TableFunc *) node;                  if (walker(tf->ns_uris, context))                     return true;                 if (walker(tf->docexpr, context))                     return true;                 if (walker(tf->rowexpr, context))                     return true;                 if (walker(tf->colexprs, context))                     return true;                 if (walker(tf->coldefexprs, context))                     return true;             }             break;         default:             elog(ERROR, "unrecognized node type: %d",                  (int) nodeTag(node));             break;     }     return false; }

三、跟踪分析

测试脚本:

select *                from t_dwxx awhere exists (select b.dwbh from t_grxx b where a.dwbh = b.dwbh);

gdb分析:

(gdb) b convert_EXISTS_sublink_to_joinBreakpoint 1 at 0x77a4fe: file subselect.c, line 1426.(gdb) cContinuing.Breakpoint 1, convert_EXISTS_sublink_to_join (root=0x22de828, sublink=0x22292a0, under_not=false, available_rels=0x22defa8)    at subselect.c:14261426        Query      *parse = root->parse;(gdb) #1.root见上一节#2.sublink,子链接,见本节查询树结构#3.under_not,false,表示EXISTS#4.available_rels,可用的rels(gdb) p *available_rels$1 = {nwords = 1, words = 0x22defac}(gdb) p available_rels->words[0]$3 = 2(gdb) ...1511        rtoffset = list_length(parse->rtable);(gdb) 1512        OffsetVarNodes((Node *) subselect, rtoffset, 0);(gdb) p rtoffset$6 = 1(gdb) stepOffsetVarNodes (node=0x22dee48, offset=1, sublevels_up=0) at rewriteManip.c:428428     context.offset = offset;...#调整subselect->jointree->fromlist->head(类型为RTR)的rtindex,原为1,调整为2(gdb) p *node$20 = {type = T_RangeTblRef}(gdb) p *(RangeTblRef *)node$21 = {type = T_RangeTblRef, rtindex = 1}(gdb) n368             rtr->rtindex += context->offset;(gdb) 370         return false;(gdb) p *(RangeTblRef *)node$22 = {type = T_RangeTblRef, rtindex = 2}(gdb) 1513        OffsetVarNodes(whereClause, rtoffset, 0);(gdb) 1520        IncrementVarSublevelsUp((Node *) subselect, -1, 1);(gdb) p whereClause$23 = (Node *) 0x22def58(gdb) p *whereClause$24 = {type = T_OpExpr}(gdb) set $arg1=(RelabelType *)((OpExpr *)whereClause)->args->head->data.ptr_value(gdb) p *$arg1->arg$32 = {type = T_Var}#第1个参数是t_dwxx.dwbh(gdb) p *(Var *)$arg1->arg$33 = {xpr = {type = T_Var}, varno = 1, varattno = 2, vartype = 1043, vartypmod = 14, varcollid = 100, varlevelsup = 1,   varnoold = 1, varoattno = 2, location = 72}(gdb) set $arg2=(RelabelType *)((OpExpr *)whereClause)->args->tail->data.ptr_value#第2个参数是t_grxx.dwbh#varno/varnoold从原来的值1调整为2(gdb) p *(Var *)$arg2->arg$34 = {xpr = {type = T_Var}, varno = 2, varattno = 1, vartype = 1043, vartypmod = 14, varcollid = 100, varlevelsup = 0,   varnoold = 2, varoattno = 1, location = 81}(gdb) (gdb) n1521        IncrementVarSublevelsUp(whereClause, -1, 1);(gdb) 1529        clause_varnos = pull_varnos(whereClause);#调整varlevelsup为0(gdb) p *(Var *)$arg1->arg$36 = {xpr = {type = T_Var}, varno = 1, varattno = 2, vartype = 1043, vartypmod = 14, varcollid = 100, varlevelsup = 0,   varnoold = 1, varoattno = 2, location = 72}(gdb) p *(Var *)$arg2->arg$37 = {xpr = {type = T_Var}, varno = 2, varattno = 1, vartype = 1043, vartypmod = 14, varcollid = 100, varlevelsup = 0,   varnoold = 2, varoattno = 1, location = 81}...#构造返回值(gdb)1552        result = makeNode(JoinExpr);1566        return result;(gdb) 1567    }(gdb) pull_up_sublinks_qual_recurse (root=0x22de828, node=0x22292a0, jtlink1=0x7ffdcabbc918, available_rels1=0x22defa8,     jtlink2=0x0, available_rels2=0x0) at prepjointree.c:404404                 j->larg = *jtlink1;#上拉成为半连接SEMI JOIN(gdb) p *(JoinExpr *)jtnode$65 = {type = T_JoinExpr, jointype = JOIN_SEMI, isNatural = false, larg = 0x22e7118, rarg = 0x22e7438, usingClause = 0x0,   quals = 0x22def58, alias = 0x0, rtindex = 0}#DONE!

到此,相信大家对"PostgreSQL查询优化中怎么上拉子链接"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

0