千家信息网

PostgreSQL查询优化中对消除外连接的处理过程是什么

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,本篇内容介绍了"PostgreSQL查询优化中对消除外连接的处理过程是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细
千家信息网最后更新 2026年10月31日PostgreSQL查询优化中对消除外连接的处理过程是什么

本篇内容介绍了"PostgreSQL查询优化中对消除外连接的处理过程是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!


使用的测试脚本:

drop table if exists t_null1;create table t_null1(c1 int);insert into t_null1 values(1);insert into t_null1 values(2);insert into t_null1 values(null);drop table if exists t_null2;create table t_null2(c1 int);insert into t_null2 values(1);insert into t_null2 values(null);

一、基本概念

消除外连接的代码注释说明如下:

 /*  * reduce_outer_joins  *      Attempt to reduce outer joins to plain inner joins.  *  * The idea here is that given a query like  *      SELECT ... FROM a LEFT JOIN b ON (...) WHERE b.y = 42;  * we can reduce the LEFT JOIN to a plain JOIN if the "=" operator in WHERE  * is strict.  The strict operator will always return NULL, causing the outer  * WHERE to fail, on any row where the LEFT JOIN filled in NULLs for b's  * columns.  Therefore, there's no need for the join to produce null-extended  * rows in the first place --- which makes it a plain join not an outer join.  * (This scenario may not be very likely in a query written out by hand, but  * it's reasonably likely when pushing quals down into complex views.)  *  * More generally, an outer join can be reduced in strength if there is a  * strict qual above it in the qual tree that constrains a Var from the  * nullable side of the join to be non-null.  (For FULL joins this applies  * to each side separately.)  *  * Another transformation we apply here is to recognize cases like  *      SELECT ... FROM a LEFT JOIN b ON (a.x = b.y) WHERE b.y IS NULL;  * If the join clause is strict for b.y, then only null-extended rows could  * pass the upper WHERE, and we can conclude that what the query is really  * specifying is an anti-semijoin.  We change the join type from JOIN_LEFT  * to JOIN_ANTI.  The IS NULL clause then becomes redundant, and must be  * removed to prevent bogus selectivity calculations, but we leave it to  * distribute_qual_to_rels to get rid of such clauses.  *  * Also, we get rid of JOIN_RIGHT cases by flipping them around to become  * JOIN_LEFT.  This saves some code here and in some later planner routines,  * but the main reason to do it is to not need to invent a JOIN_REVERSE_ANTI  * join type.  *  * To ease recognition of strict qual clauses, we require this routine to be  * run after expression preprocessing (i.e., qual canonicalization and JOIN  * alias-var expansion).  */

有两种类型的外连接可以被消除,第一种是形如以下形式的语句:
SELECT ... FROM a LEFT JOIN b ON (...) WHERE b.y = 42;
这种语句如满足条件可变换为内连接(INNER_JOIN).
之所以可以变换为内连接,那是因为这样的语句与内连接处理的结果是一样的,原因是在Nullable-Side端(需要填充NULL值的一端),存在过滤条件保证这一端不可能是NULL值,比如IS NOT NULL/y = 42这类强(strict)过滤条件.

testdb=# explain verbose select * from t_null1 a left join t_null2 b on a.c1 = b.c1;                                   QUERY PLAN                                   -------------------------------------------------------------------------------- Merge Left Join  (cost=359.57..860.00 rows=32512 width=8) -- 外连接   Output: a.c1, b.c1   Merge Cond: (a.c1 = b.c1)   ->  Sort  (cost=179.78..186.16 rows=2550 width=4)         Output: a.c1         Sort Key: a.c1         ->  Seq Scan on public.t_null1 a  (cost=0.00..35.50 rows=2550 width=4)               Output: a.c1   ->  Sort  (cost=179.78..186.16 rows=2550 width=4)         Output: b.c1         Sort Key: b.c1         ->  Seq Scan on public.t_null2 b  (cost=0.00..35.50 rows=2550 width=4)               Output: b.c1(13 rows)testdb=# explain verbose select * from t_null1 a left join t_null2 b on a.c1 = b.c1 where b.c1 = 1;                                  QUERY PLAN                                  ------------------------------------------------------------------------------ Nested Loop  (cost=0.00..85.89 rows=169 width=8) -- 外连接(Left关键字)已被消除   Output: a.c1, b.c1   ->  Seq Scan on public.t_null1 a  (cost=0.00..41.88 rows=13 width=4)         Output: a.c1         Filter: (a.c1 = 1)   ->  Materialize  (cost=0.00..41.94 rows=13 width=4)         Output: b.c1         ->  Seq Scan on public.t_null2 b  (cost=0.00..41.88 rows=13 width=4)               Output: b.c1               Filter: (b.c1 = 1)(10 rows)

第二种形如:
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=# explain verbose select * from t_null1 a left join t_null2 b on a.c1 = b.c1 where b.c1 is null;                                   QUERY PLAN                                   -------------------------------------------------------------------------------- Hash Anti Join  (cost=67.38..152.44 rows=1275 width=8) -- 变换为反连接   Output: a.c1, b.c1   Hash Cond: (a.c1 = b.c1)   ->  Seq Scan on public.t_null1 a  (cost=0.00..35.50 rows=2550 width=4)         Output: a.c1   ->  Hash  (cost=35.50..35.50 rows=2550 width=4)         Output: b.c1         ->  Seq Scan on public.t_null2 b  (cost=0.00..35.50 rows=2550 width=4)               Output: b.c1(9 rows)

值得一提的是,在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函数实现,代码片段如下:

     /*      * If we have any outer joins, try to reduce them to plain inner joins.      * This step is most easily done after we've done expression      * preprocessing.      */     if (hasOuterJoins)         reduce_outer_joins(root);

reduce_outer_joins


相关的数据结构和依赖的子函数:
reduce_outer_joins_state

 typedef struct reduce_outer_joins_state {     Relids      relids;         /* base relids within this subtree */     bool        contains_outer; /* does subtree contain outer join(s)? */     List       *sub_states;     /* List of states for subtree components */ } reduce_outer_joins_state;

BitmapXX

 typedef struct Bitmapset {     int         nwords;         /* number of words in array */     bitmapword  words[FLEXIBLE_ARRAY_MEMBER];   /* really [nwords] */ } Bitmapset;  #define WORDNUM(x)  ((x) / BITS_PER_BITMAPWORD) #define BITNUM(x)   ((x) % BITS_PER_BITMAPWORD) /* The unit size can be adjusted by changing these three declarations: */ #define BITS_PER_BITMAPWORD 32 typedef uint32 bitmapword; /* must be an unsigned type */ /*  * bms_make_singleton - build a bitmapset containing a single member  */ Bitmapset * bms_make_singleton(int x) {     Bitmapset  *result;     int         wordnum,                 bitnum;      if (x < 0)         elog(ERROR, "negative bitmapset member not allowed");     wordnum = WORDNUM(x);     bitnum = BITNUM(x);     result = (Bitmapset *) palloc0(BITMAPSET_SIZE(wordnum + 1));     result->nwords = wordnum + 1;     result->words[wordnum] = ((bitmapword) 1 << bitnum);     return result; } /*  * bms_add_member - add a specified member to set  *  * Input set is modified or recycled!  */ Bitmapset * bms_add_member(Bitmapset *a, int x) {     int         wordnum,                 bitnum;      if (x < 0)         elog(ERROR, "negative bitmapset member not allowed");     if (a == NULL)         return bms_make_singleton(x);     wordnum = WORDNUM(x);     bitnum = BITNUM(x);      /* enlarge the set if necessary */     if (wordnum >= a->nwords)     {         int         oldnwords = a->nwords;         int         i;          a = (Bitmapset *) repalloc(a, BITMAPSET_SIZE(wordnum + 1));         a->nwords = wordnum + 1;         /* zero out the enlarged portion */         for (i = oldnwords; i < a->nwords; i++)             a->words[i] = 0;     }      a->words[wordnum] |= ((bitmapword) 1 << bitnum);     return a; }

find_nonnullable_rels

 /*  * find_nonnullable_rels  *      Determine which base rels are forced nonnullable by given clause.  *  * Returns the set of all Relids that are referenced in the clause in such  * a way that the clause cannot possibly return TRUE if any of these Relids  * is an all-NULL row.  (It is OK to err on the side of conservatism; hence  * the analysis here is simplistic.)  *  * The semantics here are subtly different from contain_nonstrict_functions:  * that function is concerned with NULL results from arbitrary expressions,  * but here we assume that the input is a Boolean expression, and wish to  * see if NULL inputs will provably cause a FALSE-or-NULL result.  We expect  * the expression to have been AND/OR flattened and converted to implicit-AND  * format.  *  * Note: this function is largely duplicative of find_nonnullable_vars().  * The reason not to simplify this function into a thin wrapper around  * find_nonnullable_vars() is that the tested conditions really are different:  * a clause like "t1.v1 IS NOT NULL OR t1.v2 IS NOT NULL" does not prove  * that either v1 or v2 can't be NULL, but it does prove that the t1 row  * as a whole can't be all-NULL.  *  * top_level is true while scanning top-level AND/OR structure; here, showing  * the result is either FALSE or NULL is good enough.  top_level is false when  * we have descended below a NOT or a strict function: now we must be able to  * prove that the subexpression goes to NULL.  *  * We don't use expression_tree_walker here because we don't want to descend  * through very many kinds of nodes; only the ones we can be sure are strict.  */ Relids find_nonnullable_rels(Node *clause) {     return find_nonnullable_rels_walker(clause, true); }  static Relids find_nonnullable_rels_walker(Node *node, bool top_level) {     Relids      result = NULL;     ListCell   *l;      if (node == NULL)         return NULL;     if (IsA(node, Var))     {         Var        *var = (Var *) node;          if (var->varlevelsup == 0)             result = bms_make_singleton(var->varno);     }     else if (IsA(node, List))     {         /*          * At top level, we are examining an implicit-AND list: if any of the          * arms produces FALSE-or-NULL then the result is FALSE-or-NULL. If          * not at top level, we are examining the arguments of a strict          * function: if any of them produce NULL then the result of the          * function must be NULL.  So in both cases, the set of nonnullable          * rels is the union of those found in the arms, and we pass down the          * top_level flag unmodified.          */         foreach(l, (List *) node)         {             result = bms_join(result,                               find_nonnullable_rels_walker(lfirst(l),                                                            top_level));         }     }     else if (IsA(node, FuncExpr))     {         FuncExpr   *expr = (FuncExpr *) node;          if (func_strict(expr->funcid))             result = find_nonnullable_rels_walker((Node *) expr->args, false);     }     else if (IsA(node, OpExpr))     {         OpExpr     *expr = (OpExpr *) node;          set_opfuncid(expr);         if (func_strict(expr->opfuncid))             result = find_nonnullable_rels_walker((Node *) expr->args, false);     }     else if (IsA(node, ScalarArrayOpExpr))     {         ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;          if (is_strict_saop(expr, true))             result = find_nonnullable_rels_walker((Node *) expr->args, false);     }     else if (IsA(node, BoolExpr))     {         BoolExpr   *expr = (BoolExpr *) node;          switch (expr->boolop)         {             case AND_EXPR:                 /* At top level we can just recurse (to the List case) */                 if (top_level)                 {                     result = find_nonnullable_rels_walker((Node *) expr->args,                                                           top_level);                     break;                 }                  /*                  * Below top level, even if one arm produces NULL, the result                  * could be FALSE (hence not NULL).  However, if *all* the                  * arms produce NULL then the result is NULL, so we can take                  * the intersection of the sets of nonnullable rels, just as                  * for OR.  Fall through to share code.                  */                 /* FALL THRU */             case OR_EXPR:                  /*                  * OR is strict if all of its arms are, so we can take the                  * intersection of the sets of nonnullable rels for each arm.                  * This works for both values of top_level.                  */                 foreach(l, expr->args)                 {                     Relids      subresult;                      subresult = find_nonnullable_rels_walker(lfirst(l),                                                              top_level);                     if (result == NULL) /* first subresult? */                         result = subresult;                     else                         result = bms_int_members(result, subresult);                      /*                      * If the intersection is empty, we can stop looking. This                      * also justifies the test for first-subresult above.                      */                     if (bms_is_empty(result))                         break;                 }                 break;             case NOT_EXPR:                 /* NOT will return null if its arg is null */                 result = find_nonnullable_rels_walker((Node *) expr->args,                                                       false);                 break;             default:                 elog(ERROR, "unrecognized boolop: %d", (int) expr->boolop);                 break;         }     }     else if (IsA(node, RelabelType))     {         RelabelType *expr = (RelabelType *) node;          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);     }     else if (IsA(node, CoerceViaIO))     {         /* not clear this is useful, but it can't hurt */         CoerceViaIO *expr = (CoerceViaIO *) node;          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);     }     else if (IsA(node, ArrayCoerceExpr))     {         /* ArrayCoerceExpr is strict at the array level; ignore elemexpr */         ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);     }     else if (IsA(node, ConvertRowtypeExpr))     {         /* not clear this is useful, but it can't hurt */         ConvertRowtypeExpr *expr = (ConvertRowtypeExpr *) node;          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);     }     else if (IsA(node, CollateExpr))     {         CollateExpr *expr = (CollateExpr *) node;          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);     }     else if (IsA(node, NullTest))     {         /* IS NOT NULL can be considered strict, but only at top level */         NullTest   *expr = (NullTest *) node;          if (top_level && expr->nulltesttype == IS_NOT_NULL && !expr->argisrow)             result = find_nonnullable_rels_walker((Node *) expr->arg, false);     }     else if (IsA(node, BooleanTest))     {         /* Boolean tests that reject NULL are strict at top level */         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);     }     else if (IsA(node, PlaceHolderVar))     {         PlaceHolderVar *phv = (PlaceHolderVar *) node;          result = find_nonnullable_rels_walker((Node *) phv->phexpr, top_level);     }     return result; }

三、跟踪分析

外连接->内连接
测试脚本:

select * from t_null1 a left join t_null2 b on a.c1 = b.c1 where b.c1 = 1;

gdb跟踪:

(gdb) b reduce_outer_joinsBreakpoint 1 at 0x77faf5: file prepjointree.c, line 2484.(gdb) cContinuing.Breakpoint 1, reduce_outer_joins (root=0x2eafa98) at prepjointree.c:24842484    state = reduce_outer_joins_pass1((Node *) root->parse->jointree);(gdb) b reduce_outer_joinsBreakpoint 1 at 0x77faf5: file prepjointree.c, line 2484....#进入reduce_outer_joins_pass1(gdb) stepreduce_outer_joins_pass1 (jtnode=0x2ea4af8) at prepjointree.c:2504...(gdb) p *f$2 = {type = T_FromExpr, fromlist = 0x2ea4668, quals = 0x2edcae8}#进入FromExpr分支(gdb) n2527        sub_state = reduce_outer_joins_pass1(lfirst(l));##递归调用(gdb) stepreduce_outer_joins_pass1 (jtnode=0x2dd40f0) at prepjointree.c:25042504    result = (reduce_outer_joins_state *)##进入JoinExpr分支2534    else if (IsA(jtnode, JoinExpr))(gdb) 2536      JoinExpr   *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}###递归调用2543      sub_state = reduce_outer_joins_pass1(j->larg);(gdb) stepreduce_outer_joins_pass1 (jtnode=0x2dd49e0) at prepjointree.c:25042504    result = (reduce_outer_joins_state *)###进入RangeTblRef分支2512    if (IsA(jtnode, RangeTblRef))(gdb) 2514      int     varno = ((RangeTblRef *) jtnode)->rtindex;...###JoinExpr处理完毕2549      sub_state = reduce_outer_joins_pass1(j->rarg);(gdb) 2551                       sub_state->relids);(gdb) 2550      result->relids = bms_add_members(result->relids,(gdb) 2552      result->contains_outer |= sub_state->contains_outer;(gdb) 2553      result->sub_states = lappend(result->sub_states, sub_state);(gdb) 2558    return result;(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...2523      foreach(l, f->fromlist)(gdb) p *result$18 = {relids = 0x2edcc60, contains_outer = true, sub_states = 0x2edcc98}#回到reduce_outer_joinsreduce_outer_joins (root=0x2eafa98) at prepjointree.c:24872487    if (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) at prepjointree.c:25832583    if (jtnode == NULL)...#进入FromExpr分支2587    else if (IsA(jtnode, FromExpr))(gdb) 2589      FromExpr   *f = (FromExpr *) jtnode;...#寻找FromExpr中存在过滤条件为NOT NULL的Relids(gdb) n2597      pass_nonnullable_rels = find_nonnullable_rels(f->quals);(gdb) 2598      pass_nonnullable_rels = bms_add_members(pass_nonnullable_rels,(gdb) p pass_nonnullable_rels->words[0]$34 = 4 -- rtindex = 2的Relid#寻找NOT NULL's Vars2601      pass_nonnullable_vars = find_nonnullable_vars(f->quals);(gdb) 2602      pass_nonnullable_vars = list_concat(pass_nonnullable_vars,(gdb) 2604      pass_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) 2614          reduce_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) at prepjointree.c:25832583    if (jtnode == NULL)##进入JoinExpr分支2622    else if (IsA(jtnode, JoinExpr))(gdb) 2624      JoinExpr   *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) p rtindex$46 = 3 -->Jointree整体作为3号RTE存在...2633      switch (jointype)(gdb) 2638          if (bms_overlap(nonnullable_rels, right_state->relids))(gdb) p *nonnullable_rels->words$49 = 4 -->2号RTE(gdb) p right_state->relids->words[0]$50 = 4 -->2号RTE##转换为内连接(gdb) n2639            jointype = JOIN_INNER;...##修改RTE的连接类型(gdb) 2724      if (rtindex && jointype != j->jointype)(gdb)2726        RangeTblEntry *rte = rt_fetch(rtindex, root->parse->rtable);(gdb) 2730        rte->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) n2732      j->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) at prepjointree.c:26092609      forboth(l, f->fromlist, s, state->sub_states)#回到主函数reduce_outer_joins2844  }(gdb) reduce_outer_joins (root=0x2eafa98) at prepjointree.c:24922492  }(gdb) #DONE!

外连接->反连接
测试脚本:

select * from t_null1 a left join t_null2 b on a.c1 = b.c1 where b.c1 is null;

gdb跟踪,与转换为内连接不同的地方在于reduce_outer_joins_pass2函数中find_forced_null_vars在这里是可以找到相应的Vars的:

(gdb) b prepjointree.c:2702Breakpoint 3 at 0x78023c: file prepjointree.c, line 2702.(gdb) cContinuing.Breakpoint 3, reduce_outer_joins_pass2 (jtnode=0x2dd40f0, state=0x2edc9b8, root=0x2eafa98, nonnullable_rels=0x0,     nonnullable_vars=0x0, forced_null_vars=0x2edcb70) at prepjointree.c:27032703      if (jointype == JOIN_LEFT)#尝试转换为内连接,但不成功,仍为左连接2707        local_nonnullable_vars = find_nonnullable_vars(j->quals);(gdb) 2708        computed_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}...#转换为反连接2717        if (overlap != NIL &&(gdb) 2720          jointype = JOIN_ANTI;(gdb) cContinuing.

"PostgreSQL查询优化中对消除外连接的处理过程是什么"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

条件 b.y 函数 语句 处理 查询 分支 变换 过程 上层 代码 类型 脚本 a.x 递归 测试 跟踪 一端 内容 更多 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 科技互联网技术 有马互联网科技 狮子座互联网科技培训 深圳卡尔迅互联网科技有限公司怎么样 中国最先进互联网科技公司 互联网和高科技公司 顶级互联网科技成果有哪些 受益于互联网科技的高速发展 赶超互联网科技 微信多开 互联网科技人工智能实验室 互联网新科技动漫ppt素材 西安小鹰速贷互联网科技有限公司 晋城互联网科技 沐思想互联网科技有限公司 21世纪互联网科技成就 九思连强互联网电子科技有限公司 互联网科技手机排行榜 互联网领先科技活动 互联网科技和医药行业 现代互联网科技公司 南京欣网互联网络科技有限公司 互联网科技官网 广州互联网科技公司 互联网金融 科技金融 中国互联网公司排名锤子科技 三亚科技创新互联网基地 青岛易易互联网科技 承德科技产业互联网平台 中国主要互联网科技公司 武汉亿视互联网科技有限公司
0