PostgreSQL 源码解读(233)- 查询#126(NOT IN实现#4)
发表于:2025-11-07 作者:千家信息网编辑
千家信息网最后更新 2025年11月07日,本节简单解释了PostgreSQL NOT IN在执行时为何会出现时快时慢的现象。测试数据如下:[local]:5432 pg12@testdb=# select count(*) from tbl;
千家信息网最后更新 2025年11月07日PostgreSQL 源码解读(233)- 查询#126(NOT IN实现#4)
本节简单解释了PostgreSQL NOT IN在执行时为何会出现时快时慢的现象。
测试数据如下:
[local]:5432 pg12@testdb=# select count(*) from tbl; count ------- 1(1 row)Time: 6.009 ms[local]:5432 pg12@testdb=# select count(*) from t_big_null; count ---------- 10000001(1 row)Time: 633.248 ms[local]:5432 pg12@testdb=# \d tbl Table "public.tbl" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | not null | value | integer | | not null | Indexes: "tbl_pkey" PRIMARY KEY, btree (id)Rules: rule_tbl_update AS ON INSERT TO tbl WHERE (EXISTS ( SELECT tbl_1.id, tbl_1.value FROM tbl tbl_1 WHERE tbl_1.id = new.id)) DO INSTEAD UPDATE tbl SET value = tbl.value + 1 WHERE tbl.id = new.id[local]:5432 pg12@testdb=# \d t_big_null Table "public.t_big_null" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | | [local]:5432 pg12@testdb=#注意tbl表只有一行数据(id = 1),而t_big_null表在插入"id = 1"这一行时有意放在最后才插入
truncate table t_big_null;insert into t_big_null select generate_series(2,10000000);insert into t_big_null values(1);一、数据结构
SubPlanState
子计划运行期状态
/* ---------------- * SubPlanState node * ---------------- */typedef struct SubPlanState{ NodeTag type; SubPlan *subplan; /* expression plan node */ struct PlanState *planstate; /* subselect plan's state tree */ struct PlanState *parent; /* parent plan node's state tree */ ExprState *testexpr; /* 组合表达式状态;state of combining expression */ List *args; /* 参数表达式状态;states of argument _expression(s) */ HeapTuple curTuple; /* subplan最近的元组;copy of most recent tuple from subplan */ Datum curArray; /* most recent array from ARRAY() subplan */ /* these are used when hashing the subselect's output: */ TupleDesc descRight; /* 投影后的子查询描述符;subselect desc after projection */ ProjectionInfo *projLeft; /* for projecting lefthand exprs */ ProjectionInfo *projRight; /* for projecting subselect output */ TupleHashTable hashtable; /* hash table for no-nulls subselect rows */ TupleHashTable hashnulls; /* hash table for rows with null(s) */ bool havehashrows; /* true if hashtable is not empty */ bool havenullrows; /* true if hashnulls is not empty */ MemoryContext hashtablecxt; /* memory context containing hash tables */ MemoryContext hashtempcxt; /* temp memory context for hash tables */ ExprContext *innerecontext; /* econtext for computing inner tuples */ AttrNumber *keyColIdx; /* control data for hash tables */ Oid *tab_eq_funcoids; /* equality func oids for table * datatype(s) */ Oid *tab_collations; /* collations for hash and comparison */ FmgrInfo *tab_hash_funcs; /* hash functions for table datatype(s) */ FmgrInfo *tab_eq_funcs; /* equality functions for table datatype(s) */ FmgrInfo *lhs_hash_funcs; /* hash functions for lefthand datatype(s) */ FmgrInfo *cur_eq_funcs; /* equality functions for LHS vs. table */ ExprState *cur_eq_comp; /* equality comparator for LHS vs. table */} SubPlanState;SubPlan
子查询计划
/* * SubPlan - executable expression node for a subplan (sub-SELECT) * * The planner replaces SubLink nodes in expression trees with SubPlan * nodes after it has finished planning the subquery. SubPlan references * a sub-plantree stored in the subplans list of the toplevel PlannedStmt. * (We avoid a direct link to make it easier to copy expression trees * without causing multiple processing of the subplan.) * 查询规划器在完成子查询的规划后使用SubPlan节点替换表达式树中的SubLink节点。 * SubPlan引用了存储在高层PlannedStmt中的subplans链表中的sub-plantree。 * (避免使用直接链接,从而使得拷贝表达式树相对比较简单) * * In an ordinary subplan, testexpr points to an executable expression * (OpExpr, an AND/OR tree of OpExprs, or RowCompareExpr) for the combining * operator(s); the left-hand arguments are the original lefthand expressions, * and the right-hand arguments are PARAM_EXEC Param nodes representing the * outputs of the sub-select. (NOTE: runtime coercion functions may be * inserted as well.) This is just the same expression tree as testexpr in * the original SubLink node, but the PARAM_SUBLINK nodes are replaced by * suitably numbered PARAM_EXEC nodes. * 常规情况下,testexpr指向用于组合操作的可执行表达式(OpExpr、OpExprs的AND/OR树或者RowCompareExpr); * 左参数是原始的左表达式,右参数是PARAM_EXEC参数节点用以表示子查询的输出。 * 与原始SubLink节点的testexpr具有相同的表达式树,但PARAM_SUBLINK节点则使用合适的已编号PARAM_EXEC节点替代。 * * If the sub-select becomes an initplan rather than a subplan, the executable * expression is part of the outer plan's expression tree (and the SubPlan * node itself is not, but rather is found in the outer plan's initPlan * list). In this case testexpr is NULL to avoid duplication. * 如果子查询成了initplan而不是subplan,可执行的表达式是外层plan表达式树的一部分。 * 这种情况下,testexpr为NULL以避免重复。 * * The planner also derives lists of the values that need to be passed into * and out of the subplan. Input values are represented as a list "args" of * expressions to be evaluated in the outer-query context (currently these * args are always just Vars, but in principle they could be any expression). * The values are assigned to the global PARAM_EXEC params indexed by parParam * (the parParam and args lists must have the same ordering). setParam is a * list of the PARAM_EXEC params that are computed by the sub-select, if it * is an initplan; they are listed in order by sub-select output column * position. (parParam and setParam are integer Lists, not Bitmapsets, * because their ordering is significant.) * 规划器还派生了需要传入和传出子计划的值的链表。 * 输入值标识位表达式的"args"链表,在外层查询上下文中进行解析。 * (这些args通常是Vars,但原则上它们可以是任意表达式) * 这些值以parParam为索引给全局PARAM_EXEC参数赋值。 * setParam是PARAM_EXEC参数链表,通过子查询(如为initplan)计算所得。 * 它们按子查询输出列的位置进行排序组织为链表形式。 * (parParam和setParam是整型链表,而不是Bitmapsets链表) * * Also, the planner computes startup and per-call costs for use of the * SubPlan. Note that these include the cost of the subquery proper, * evaluation of the testexpr if any, and any hashtable management overhead. * 同时,规划器计算SubPlan启动和每次调用的成本。注意:包括子查询正常解析testexpr的成本以及哈希表管理成本。 */typedef struct SubPlan{ Expr xpr;//表达式 /* Fields copied from original SubLink: */ //从SubLink中拷贝而来 SubLinkType subLinkType; /* see above */ /* The combining operators, transformed to an executable expression: */ //组合操作符,转换为可执行的表达式 Node *testexpr; /* OpExpr or RowCompareExpr expression tree */ List *paramIds; /* 参数IDs;IDs of Params embedded in the above */ /* Identification of the Plan tree to use: */ //Plan tree标识 int plan_id; /* Index (from 1) in PlannedStmt.subplans */ /* Identification of the SubPlan for EXPLAIN and debugging purposes: */ //EXPLAIN和debug目的的SubPlan标识 char *plan_name; /* A name assigned during planning */ /* Extra data useful for determining subplan's output type: */ //用于确定subplan输出类型的额外信息 Oid firstColType; /* subplan结果的第一个列类型;Type of first column of subplan result */ int32 firstColTypmod; /* 第一列的Typmod;Typmod of first column of subplan result */ Oid firstColCollation; /* 第一列的Collation;Collation of first column of subplan * result */ /* Information about execution strategy: */ //执行阶段的相关信息 bool useHashTable; /* 是否使用哈希表存储子查询输出;true to store subselect output in a hash * table (implies we are doing "IN") */ bool unknownEqFalse; /* 如OK为T,如为未知则为F;快速处理null值;true if it's okay to return FALSE when the * spec result is UNKNOWN; this allows much * simpler handling of null values */ bool parallel_safe; /* 是否并行安全?is the subplan parallel-safe? */ /* Note: parallel_safe does not consider contents of testexpr or args */ /* Information for passing params into and out of the subselect: */ //用于给子查询传入和传出参数的信息 /* setParam and parParam are lists of integers (param IDs) */ //setParam和parParam是整型链表(param IDs) List *setParam; /* initplan subqueries have to set these * Params for parent plan */ List *parParam; /* indices of input Params from parent plan */ List *args; /* 以parParam值进行传递的表达式;exprs to pass as parParam values */ /* Estimated execution costs: */ //估算执行成本 Cost startup_cost; /* one-time setup cost */ Cost per_call_cost; /* cost for each subplan evaluation */} SubPlan;SubLinkType
SubLink类型
/* * SubLink * * A SubLink represents a subselect appearing in an expression, and in some * cases also the combining operator(s) just above it. The subLinkType * indicates the form of the expression represented: * EXISTS_SUBLINK EXISTS(SELECT ...) * ALL_SUBLINK (lefthand) op ALL (SELECT ...) * ANY_SUBLINK (lefthand) op ANY (SELECT ...) * ROWCOMPARE_SUBLINK (lefthand) op (SELECT ...) * EXPR_SUBLINK (SELECT with single targetlist item ...) * MULTIEXPR_SUBLINK (SELECT with multiple targetlist items ...) * ARRAY_SUBLINK ARRAY(SELECT with single targetlist item ...) * CTE_SUBLINK WITH query (never actually part of an expression) * 我们使用SubLink表示在表达式中出现的子查询,在某些情况下组合操作符会出现在SubLink之上。 * subLinkType表示表达式的形式: * EXISTS_SUBLINK EXISTS(SELECT ...) * ALL_SUBLINK (lefthand) op ALL (SELECT ...) * ANY_SUBLINK (lefthand) op ANY (SELECT ...) * ROWCOMPARE_SUBLINK (lefthand) op (SELECT ...) * EXPR_SUBLINK (SELECT with single targetlist item ...) * MULTIEXPR_SUBLINK (SELECT with multiple targetlist items ...) * ARRAY_SUBLINK ARRAY(SELECT with single targetlist item ...) * CTE_SUBLINK WITH query (never actually part of an expression) * * For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the * same length as the subselect's targetlist. ROWCOMPARE will *always* have * a list with more than one entry; if the subselect has just one target * then the parser will create an EXPR_SUBLINK instead (and any operator * above the subselect will be represented separately). * ROWCOMPARE, EXPR, and MULTIEXPR require the subselect to deliver at most * one row (if it returns no rows, the result is NULL). * ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean * results. ALL and ANY combine the per-row results using AND and OR * semantics respectively. * ARRAY requires just one target column, and creates an array of the target * column's type using any number of rows resulting from the subselect. * 对于ALL,ANY和ROWCOMPARE,左操作符是与子查询目标链表长度一致的表达式链表。 * ROWCOMPARE通常有超过一个条目的链表;如果子查询刚好只有一个目标列,那么解析器会创建EXPR_SUBLINK * (同时所有在子查询之上的操作符会单独表示) * ROWCOMPARE, EXPR, 和MULTIEXPR要求子查询至少输出一行(如返回0行,则结果为NULL)。 * ALL,ANY和ROWCOMPARE要求组合操作符输出布尔型结果。 * ALL/ANY使用AND/OR语义来组合每一行的结果。 * * SubLink is classed as an Expr node, but it is not actually executable; * it must be replaced in the expression tree by a SubPlan node during * planning. * SubLink归类为Expr节点,但实际上并不是可执行的,必须在计划阶段通过SubPlan替代。 * * NOTE: in the raw output of gram.y, testexpr contains just the raw form * of the lefthand _expression (if any), and operName is the String name of * the combining operator. Also, subselect is a raw parsetree. During parse * analysis, the parser transforms testexpr into a complete boolean expression * that compares the lefthand value(s) to PARAM_SUBLINK nodes representing the * output columns of the subselect. And subselect is transformed to a Query. * This is the representation seen in saved rules and in the rewriter. * 注意:在gram.y的裸输出中,testexpr只包含左表达式的裸形式,operName是组合操作符的字符串名称。 * 同时,子查询是裸parsetree。在解析分析期间, * 解析器转换testexpr为完整的布尔表达式用于比较左操作符值与PARAM_SUBLINK节点所代表的子查询输出列值。 * 子查询会转换为Query结构体。 * 在已存储的规则和重写时可见的表示形式。 * * In EXISTS, EXPR, MULTIEXPR, and ARRAY SubLinks, testexpr and operName * are unused and are always null. * 在EXISTS/EXPR/MULTEXPR/ARRAY SubLinks中,testexpr和operName不再使用通常是NULL值。 * * subLinkId is currently used only for MULTIEXPR SubLinks, and is zero in * other SubLinks. This number identifies different multiple-assignment * subqueries within an UPDATE statement's SET list. It is unique only * within a particular targetlist. The output column(s) of the MULTIEXPR * are referenced by PARAM_MULTIEXPR Params appearing elsewhere in the tlist. * subLinkId当前只用于MULTIEXPR,在其他SubLinks中取值为0. * 该数字标识了在UPDATE语句SET链表中不同的多个赋值子查询。 * 只有在特定的targetlist内是唯一的。 * 出现在tlist其他地方的PARAM_MULTIEXPR参数依赖于MULTIEXPR的输出列。 * * The CTE_SUBLINK case never occurs in actual SubLink nodes, but it is used * in SubPlans generated for WITH subqueries. * CTE_SUBLINK不会出现在实际的SubLink节点中,但用于WITH子查询所产生的SubPlans中。 */typedef enum SubLinkType{ EXISTS_SUBLINK, ALL_SUBLINK, ANY_SUBLINK, ROWCOMPARE_SUBLINK, EXPR_SUBLINK, MULTIEXPR_SUBLINK, ARRAY_SUBLINK, CTE_SUBLINK /* 仅用于SubPlans中;for SubPlans only */} SubLinkType;SubLink
SubLink结构体
typedef struct SubLink{ Expr xpr; SubLinkType subLinkType; /* see above */ int subLinkId; /* ID (1..n); 0 if not MULTIEXPR */ Node *testexpr; /* outer-query test for ALL/ANY/ROWCOMPARE */ List *operName; /* originally specified operator name */ Node *subselect; /* subselect as Query* or raw parsetree */ int location; /* token location, or -1 if unknown */} SubLink;MaterialState
Material状态
/* ---------------- * MaterialState information * * materialize nodes are used to materialize the results * of a subplan into a temporary file. * materialize节点用于物化subplan的结果为临时文件。 * * ss.ss_ScanTupleSlot refers to output of underlying plan. * ss.ss_ScanTupleSlot指向underlyling plan的输出(subplan) * ---------------- */typedef struct MaterialState{ ScanState ss; /* its first field is NodeTag */ int eflags; /* 传递给tuplestore的capability标记;capability flags to pass to tuplestore */ bool eof_underlying; /* 已经到达underlying plan的末尾?reached end of underlying plan? */ Tuplestorestate *tuplestorestate;} MaterialState;Tuplestorestate
Tuplestore相关操作的私有状态。
/* * Possible states of a Tuplestore object. These denote the states that * persist between calls of Tuplestore routines. */typedef enum{ TSS_INMEM, /* Tuples still fit in memory */ TSS_WRITEFILE, /* Writing to temp file */ TSS_READFILE /* Reading from temp file */} TupStoreStatus;/* * Private state of a Tuplestore operation. */struct Tuplestorestate{ TupStoreStatus status; /* 状态枚举值;enumerated value as shown above */ int eflags; /* capability flags (OR of pointers' flags) */ bool backward; /* store extra length words in file? */ bool interXact; /* keep open through transactions? */ bool truncated; /* tuplestore_trim has removed tuples? */ int64 availMem; /* remaining memory available, in bytes */ int64 allowedMem; /* total memory allowed, in bytes */ int64 tuples; /* number of tuples added */ BufFile *myfile; /* underlying file, or NULL if none */ MemoryContext context; /* memory context for holding tuples */ ResourceOwner resowner; /* resowner for holding temp files */ /* * These function pointers decouple the routines that must know what kind * of tuple we are handling from the routines that don't need to know it. * They are set up by the tuplestore_begin_xxx routines. * * (Although tuplestore.c currently only supports heap tuples, I've copied * this part of tuplesort.c so that extension to other kinds of objects * will be easy if it's ever needed.) * * Function to copy a supplied input tuple into palloc'd space. (NB: we * assume that a single pfree() is enough to release the tuple later, so * the representation must be "flat" in one palloc chunk.) state->availMem * must be decreased by the amount of space used. */ void *(*copytup) (Tuplestorestate *state, void *tup); /* * Function to write a stored tuple onto tape. The representation of the * tuple on tape need not be the same as it is in memory; requirements on * the tape representation are given below. After writing the tuple, * pfree() it, and increase state->availMem by the amount of memory space * thereby released. */ void (*writetup) (Tuplestorestate *state, void *tup); /* * Function to read a stored tuple from tape back into memory. 'len' is * the already-read length of the stored tuple. Create and return a * palloc'd copy, and decrease state->availMem by the amount of memory * space consumed. */ void *(*readtup) (Tuplestorestate *state, unsigned int len); /* * This array holds pointers to tuples in memory if we are in state INMEM. * In states WRITEFILE and READFILE it's not used. * * When memtupdeleted > 0, the first memtupdeleted pointers are already * released due to a tuplestore_trim() operation, but we haven't expended * the effort to slide the remaining pointers down. These unused pointers * are set to NULL to catch any invalid accesses. Note that memtupcount * includes the deleted pointers. */ void **memtuples; /* array of pointers to palloc'd tuples */ int memtupdeleted; /* the first N slots are currently unused */ int memtupcount; /* number of tuples currently present */ int memtupsize; /* allocated length of memtuples array */ bool growmemtuples; /* memtuples' growth still underway? */ /* * These variables are used to keep track of the current positions. * * In state WRITEFILE, the current file seek position is the write point; * in state READFILE, the write position is remembered in writepos_xxx. * (The write position is the same as EOF, but since BufFileSeek doesn't * currently implement SEEK_END, we have to remember it explicitly.) */ TSReadPointer *readptrs; /* array of read pointers */ int activeptr; /* index of the active read pointer */ int readptrcount; /* number of pointers currently valid */ int readptrsize; /* allocated length of readptrs array */ int writepos_file; /* file# (valid if READFILE state) */ off_t writepos_offset; /* offset (valid if READFILE state) */};#define COPYTUP(state,tup) ((*(state)->copytup) (state, tup))#define WRITETUP(state,tup) ((*(state)->writetup) (state, tup))#define READTUP(state,len) ((*(state)->readtup) (state, len))#define LACKMEM(state) ((state)->availMem < 0)#define USEMEM(state,amt) ((state)->availMem -= (amt))#define FREEMEM(state,amt) ((state)->availMem += (amt))TSReadPointer
tuplestore读指针
/* * Possible states of a Tuplestore object. These denote the states that * persist between calls of Tuplestore routines. */typedef enum{ TSS_INMEM, /* Tuples still fit in memory */ TSS_WRITEFILE, /* Writing to temp file */ TSS_READFILE /* Reading from temp file */} TupStoreStatus;/* * State for a single read pointer. If we are in state INMEM then all the * read pointers' "current" fields denote the read positions. In state * WRITEFILE, the file/offset fields denote the read positions. In state * READFILE, inactive read pointers have valid file/offset, but the active * read pointer implicitly has position equal to the temp file's seek position. * * Special case: if eof_reached is true, then the pointer's read position is * implicitly equal to the write position, and current/file/offset aren't * maintained. This way we need not update all the read pointers each time * we write. */typedef struct{ int eflags; /* capability flags */ bool eof_reached; /* read has reached EOF */ int current; /* next array index to read */ int file; /* temp file# */ off_t offset; /* byte offset in file */} TSReadPointer;二、源码解读
NOT IN在实际执行时会转换为ALL_SUBLINK,执行的快慢取决于什么时候会碰到符合条件的记录,一旦遇到马上返回。因此,SQL的执行时间与数据表的扫描顺序有紧密的关系,符合条件的行越早出现,程序越早返回,需要的时间越短。
相关代码如下:
... //解析表达式 rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext, &rownull); if (subLinkType == ANY_SUBLINK) { //ANY : 使用OR语义组合 /* combine across rows per OR semantics */ if (rownull) *isNull = true; else if (DatumGetBool(rowresult)) { result = BoolGetDatum(true); *isNull = false; break; /* needn't look at any more rows */ } }如上代码所示,在全表扫描物化的时候一旦textexpr表达式测试到其中一行满足条件,那么就会马上退出循环,而这一行如果非常幸运的出现在扫描的最开始的地方,那执行时间将会很快(扫描几个数据块 vs 全表扫描)。
新建一张表,插入2条记录,其中id = 2的行出现在block编号最小的地方,而id = 1出现在block编号最大的地方,这时候pg就会出现时快时慢的情况,两者相差3个数量级。
[local]:5432 pg12@testdb=# create table tbl3(id int);CREATE TABLETime: 1.852 ms[local]:5432 pg12@testdb=# insert into tbl3 values(1);INSERT 0 1Time: 1.276 ms[local]:5432 pg12@testdb=# insert into tbl3 values(2);INSERT 0 1Time: 1.089 ms[local]:5432 pg12@testdb=# select * from tbl3 where id not in (select b.id from t_big_null b); id ----(0 rows)Time: 3.676 ms[local]:5432 pg12@testdb=# select * from tbl3 where id not in (select b.id from t_big_null b); id ----(0 rows)Time: 4925.893 ms (00:04.926)[local]:5432 pg12@testdb=# select * from tbl3 where id not in (select b.id from t_big_null b); id ----(0 rows)Time: 2.858 ms[local]:5432 pg12@testdb=# select * from tbl3 where id not in (select b.id from t_big_null b); id ----(0 rows)Time: 4588.436 ms (00:04.588)[local]:5432 pg12@testdb=# select * from tbl3 where id not in (select b.id from t_big_null b); id ----(0 rows)Time: 1.896 ms[local]:5432 pg12@testdb=# select * from tbl3 where id not in (select b.id from t_big_null b); id ----(0 rows)Time: 4653.525 ms (00:04.654)[local]:5432 pg12@testdb=#ExecScanSubPlan
/* * ExecScanSubPlan: default case where we have to rescan subplan each time * 默认情况下每次都不得不重新扫描subplan */static DatumExecScanSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull){ SubPlan *subplan = node->subplan;//子计划 PlanState *planstate = node->planstate;//计划运行期状态 SubLinkType subLinkType = subplan->subLinkType;//子链接类型 MemoryContext oldcontext;//原内存上下文 TupleTableSlot *slot;//元组slot Datum result;//结果指针 bool found = false; /* 如找到至少一个元组,则返回T;true if got at least one subplan tuple */ ListCell *pvar;//临时变量 ListCell *l;//临时变量 ArrayBuildStateAny *astate = NULL;// /* * MULTIEXPR subplans, when "executed", just return NULL; but first we * mark the subplan's output parameters as needing recalculation. (This * is a bit of a hack: it relies on the subplan appearing later in its * targetlist than any of the referencing Params, so that all the Params * have been evaluated before we re-mark them for the next evaluation * cycle. But in general resjunk tlist items appear after non-resjunk * ones, so this should be safe.) Unlike ExecReScanSetParamPlan, we do * *not* set bits in the parent plan node's chgParam, because we don't * want to cause a rescan of the parent. * * MULTIEXPR处理逻辑 */ if (subLinkType == MULTIEXPR_SUBLINK) { EState *estate = node->parent->state; foreach(l, subplan->setParam) { int paramid = lfirst_int(l); ParamExecData *prm = &(estate->es_param_exec_vals[paramid]); prm->execPlan = node; } *isNull = true; return (Datum) 0; } /* Initialize ArrayBuildStateAny in caller's context, if needed */ //数组 if (subLinkType == ARRAY_SUBLINK) astate = initArrayResultAny(subplan->firstColType, CurrentMemoryContext, true); /* * We are probably in a short-lived expression-evaluation context. Switch * to the per-query context for manipulating the child plan's chgParam, * calling ExecProcNode on it, etc. */ //切换上下文 oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); /* * Set Params of this plan from parent plan correlation values. (Any * calculation we have to do is done in the parent econtext, since the * Param values don't need to have per-query lifetime.) */ //通过父计划相关值中设置子计划参数 Assert(list_length(subplan->parParam) == list_length(node->args)); forboth(l, subplan->parParam, pvar, node->args) { int paramid = lfirst_int(l); ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar), econtext, &(prm->isnull)); planstate->chgParam = bms_add_member(planstate->chgParam, paramid); } /* * Now that we've set up its parameters, we can reset the subplan. */ //执行ReScan //Reset a plan node so that its output can be re-scanned. ExecReScan(planstate); /* * For all sublink types except EXPR_SUBLINK and ARRAY_SUBLINK, the result * is boolean as are the results of the combining operators. We combine * results across tuples (if the subplan produces more than one) using OR * semantics for ANY_SUBLINK or AND semantics for ALL_SUBLINK. * (ROWCOMPARE_SUBLINK doesn't allow multiple tuples from the subplan.) * NULL results from the combining operators are handled according to the * usual SQL semantics for OR and AND. The result for no input tuples is * FALSE for ANY_SUBLINK, TRUE for ALL_SUBLINK, NULL for * ROWCOMPARE_SUBLINK. * 除EXPR_SUBLINK和ARRAY_SUBLINK外的所有sublink,结果是布尔值(组合运算符的结果). * PG通过跨元组(如子计划产生多个元组)合并结果,对于ANY_SUBLINK使用OR语义,ALL_SUBLINK则使用AND语义. * (ROWCOMPARE_SUBLINK不允许子计划返回多个元组) * 从组合操作符中返回的NULL遵循SQL中的OR和AND语义. * 如没有输入元组,ANY_SUBLINK为FALSE,ALL_SUBLINK为TRUE,ROWCOMPARE_SUBLINK为NULL. * * For EXPR_SUBLINK we require the subplan to produce no more than one * tuple, else an error is raised. If zero tuples are produced, we return * NULL. Assuming we get a tuple, we just use its first column (there can * be only one non-junk column in this case). * 对于EXPR_SUBLINK,需要subplan产生不超过一个元组,否则报错.如果没有元组产生,返回NULL. * 假定获取到一个元组,则使用第一个列(这种情况下只有一个non-junk列). * * For ARRAY_SUBLINK we allow the subplan to produce any number of tuples, * and form an array of the first column's values. Note in particular * that we produce a zero-element array if no tuples are produced (this is * a change from pre-8.3 behavior of returning NULL). * 对于ARRAY_SUBLINK,允许subplan产生任意数目的元组,使用第一个列值组成数组. * 特别注意的是如没有元组产生则产生0个元素的数组(8.3以前是返回NULL). */ result = BoolGetDatum(subLinkType == ALL_SUBLINK);//ALL为T,否则为F *isNull = false; for (slot = ExecProcNode(planstate); !TupIsNull(slot); slot = ExecProcNode(planstate))//循环获取元组,直至没有元组为NULL(即已完成) { //元组描述符 TupleDesc tdesc = slot->tts_tupleDescriptor; Datum rowresult;//结果 bool rownull;//是否为空? int col;//列计数器 ListCell *plst;//临时变量 if (subLinkType == EXISTS_SUBLINK)//EXISTS { found = true; result = BoolGetDatum(true); break; } if (subLinkType == EXPR_SUBLINK)//EXPR表达式 { /* cannot allow multiple input tuples for EXPR sublink */ if (found) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("more than one row returned by a subquery used as an expression"))); found = true; /* * We need to copy the subplan's tuple in case the result is of * pass-by-ref type --- our return value will point into this * copied tuple! Can't use the subplan's instance of the tuple * since it won't still be valid after next ExecProcNode() call. * node->curTuple keeps track of the copied tuple for eventual * freeing. */ if (node->curTuple) heap_freetuple(node->curTuple); node->curTuple = ExecCopySlotHeapTuple(slot); result = heap_getattr(node->curTuple, 1, tdesc, isNull); /* keep scanning subplan to make sure there's only one tuple */ continue; } if (subLinkType == ARRAY_SUBLINK)//数组 { Datum dvalue; bool disnull; found = true; /* stash away current value */ Assert(subplan->firstColType == TupleDescAttr(tdesc, 0)->atttypid); dvalue = slot_getattr(slot, 1, &disnull); astate = accumArrayResultAny(astate, dvalue, disnull, subplan->firstColType, oldcontext); /* keep scanning subplan to collect all values */ continue; } /* cannot allow multiple input tuples for ROWCOMPARE sublink either */ if (subLinkType == ROWCOMPARE_SUBLINK && found)//行比较 ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("more than one row returned by a subquery used as an expression"))); found = true;//初始为T /* * For ALL, ANY, and ROWCOMPARE sublinks, load up the Params * representing the columns of the sub-select, and then evaluate the * combining expression. * 对于ALL,ANY和ROWCOMPARE子链接,加载表示子查询列的Params,并解析组合表达式 */ col = 1;//列从1计数 foreach(plst, subplan->paramIds)//循环遍历子查询参数 { int paramid = lfirst_int(plst); ParamExecData *prmdata; prmdata = &(econtext->ecxt_param_exec_vals[paramid]); Assert(prmdata->execPlan == NULL); //获取参数值 prmdata->value = slot_getattr(slot, col, &(prmdata->isnull)); //下一个列 col++; } //解析表达式 rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext, &rownull); if (subLinkType == ANY_SUBLINK) { //ANY : 使用OR语义组合 /* combine across rows per OR semantics */ if (rownull) *isNull = true; else if (DatumGetBool(rowresult)) { result = BoolGetDatum(true); *isNull = false; break; /* needn't look at any more rows */ } } else if (subLinkType == ALL_SUBLINK) { //ALL : 使用AND语义 /* combine across rows per AND semantics */ if (rownull) *isNull = true; else if (!DatumGetBool(rowresult)) { result = BoolGetDatum(false); *isNull = false; break; /* needn't look at any more rows */ } } else { /* must be ROWCOMPARE_SUBLINK */ //这里一定是ROWCOMPARE result = rowresult; *isNull = rownull; } } MemoryContextSwitchTo(oldcontext); if (subLinkType == ARRAY_SUBLINK) { /* We return the result in the caller's context */ //在调用者上下文中返回结果 result = makeArrayResultAny(astate, oldcontext, true); } else if (!found) { /* * deal with empty subplan result. result/isNull were previously * initialized correctly for all sublink types except EXPR and * ROWCOMPARE; for those, return NULL. * subplan没有结果返回,设置result&isNull值 */ if (subLinkType == EXPR_SUBLINK || subLinkType == ROWCOMPARE_SUBLINK) { result = (Datum) 0; *isNull = true; } } //返回结果 return result;}三、跟踪分析
执行SQL:
[local]:5432 pg12@testdb=# select * from tbl a where a.id not in (select b.id from t_big_null b);启动gdb跟踪,设置断点,观察到断点hit 1760次后就会退出,因此设置为忽略前1758次,只跟踪最后2次。
(gdb) info bNum Type Disp Enb Address What13 breakpoint keep y 0x0000000000721126 in ExecMaterial at nodeMaterial.c:150 breakpoint already hit 1760 times ignore next 3360 hits...(gdb) b nodeSubplan.c:328Breakpoint 17 at 0x7303b9: file nodeSubplan.c, line 328.(gdb) del 16(gdb) info bNum Type Disp Enb Address What17 breakpoint keep y 0x00000000007303b9 in ExecScanSubPlan at nodeSubplan.c:328(gdb) ignore 17 1758Will ignore next 1758 crossings of breakpoint 17.(gdb) cContinuing.开始跟踪,这是第1759次,这时候从SubPlan获取的数据是id = 10000000
Breakpoint 17, ExecScanSubPlan (node=0x3069268, econtext=0x3068aa0, isNull=0x3068dbd) at nodeSubplan.c:328328 TupleDesc tdesc = slot->tts_tupleDescriptor;(gdb) n334 if (subLinkType == EXISTS_SUBLINK)(gdb) 341 if (subLinkType == EXPR_SUBLINK)(gdb) 367 if (subLinkType == ARRAY_SUBLINK)(gdb) 383 if (subLinkType == ROWCOMPARE_SUBLINK && found)(gdb) 388 found = true;(gdb) 395 col = 1;(gdb) 396 foreach(plst, subplan->paramIds)(gdb) 398 int paramid = lfirst_int(plst);(gdb) 401 prmdata = &(econtext->ecxt_param_exec_vals[paramid]);(gdb) 402 Assert(prmdata->execPlan == NULL);(gdb) 403 prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));(gdb) p *prmdata$109 = {execPlan = 0x0, value = 9999999, isnull = false}(gdb) n404 col++;(gdb) p *prmdata$110 = {execPlan = 0x0, value = 10000000, isnull = false}(gdb) n396 foreach(plst, subplan->paramIds)(gdb)解析表达式
407 rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext,(gdb) stepExecEvalExprSwitchContext (state=0x3069380, econtext=0x3068aa0, isNull=0x7ffd184750ef) at ../../../src/include/executor/executor.h:306306 oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);(gdb) n307 retDatum = state->evalfunc(state, econtext, isNull);(gdb) stepExecInterpExpr (state=0x3069380, econtext=0x3068aa0, isnull=0x7ffd184750ef) at execExprInterp.c:404404 if (unlikely(state == NULL))(gdb) n411 op = state->steps;(gdb) p *state$111 = {tag = {type = T_ExprState}, flags = 6 '\006', resnull = false, resvalue = 0, resultslot = 0x0, steps = 0x3069418, evalfunc = 0x6e2d4d , expr = 0x30917a8, evalfunc_private = 0x6e2d4d , steps_len = 5, steps_alloc = 16, parent = 0x3068988, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) n412 resultslot = state->resultslot;(gdb) 413 innerslot = econtext->ecxt_innertuple;(gdb) 414 outerslot = econtext->ecxt_outertuple;(gdb) 415 scanslot = econtext->ecxt_scantuple;(gdb) p *innerslotCannot access memory at address 0x0(gdb) p *outerslotCannot access memory at address 0x0(gdb) n418 EEO_DISPATCH();(gdb) p *scanslot$112 = {type = T_TupleTableSlot, tts_flags = 16, tts_nvalid = 1, tts_ops = 0xc3e780 , tts_tupleDescriptor = 0x7fab449c99f0, tts_values = 0x3068bd0, tts_isnull = 0x3068be0, tts_mcxt = 0x3067da0, tts_tid = { ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 2}, tts_tableOid = 40960}(gdb) p *scanslot->tts_values$113 = 1(gdb) n448 CheckOpSlotCompatibility(op, scanslot);(gdb) n450 slot_getsomeattrs(scanslot, op->d.fetch.last_var);(gdb) 452 EEO_NEXT();(gdb) 487 int attnum = op->d.var.attnum;(gdb) 491 Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);(gdb) 492 *op->resvalue = scanslot->tts_values[attnum];(gdb) 493 *op->resnull = scanslot->tts_isnull[attnum];(gdb) 495 EEO_NEXT();(gdb) p *op->resvalue$114 = 1(gdb) n962 ExecEvalParamExec(state, op, econtext);(gdb) 964 EEO_NEXT();(gdb) p *op$115 = {opcode = 7224136, resvalue = 0x30698b8, resnull = 0x30698c0, d = {fetch = { last_var = 0, fixed = 23, known_desc = 0x0, kind = 0x0}, var = {attnum = 0, vartype = 23}, wholerow = {var = 0x1700000000, first = false, slow = false, tupdesc = 0x0, junkFilter = 0x0}, assign_var = {resultnum = 0, attnum = 23}, assign_tmp = {resultnum = 0}, constval = {value = 98784247808, isnull = false}, func = {finfo = 0x1700000000, fcinfo_data = 0x0, fn_addr = 0x0, nargs = 0}, boolexpr = {anynull = 0x1700000000, jumpdone = 0}, qualexpr = {jumpdone = 0}, jump = { jumpdone = 0}, nulltest_row = {argdesc = 0x1700000000}, param = {paramid = 0, paramtype = 23}, cparam = {paramfunc = 0x1700000000, paramarg = 0x0, paramid = 0, paramtype = 0}, casetest = {value = 0x1700000000, isnull = 0x0}, make_readonly = { value = 0x1700000000, isnull = 0x0}, iocoerce = {finfo_out = 0x1700000000, fcinfo_data_out = 0x0, finfo_in = 0x0, fcinfo_data_in = 0x0}, sqlvaluefunction = { svf = 0x1700000000}, nextvalueexpr = {seqid = 0, seqtypid = 23}, arrayexpr = { elemvalues = 0x1700000000, elemnulls = 0x0, nelems = 0, elemtype = 0, elemlength = 0, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x1700000000, resultelemtype = 0, amstate = 0x0}, row = {tupdesc = 0x1700000000, elemvalues = 0x0, elemnulls = 0x0}, rowcompare_step = { finfo = 0x1700000000, fcinfo_data = 0x0, fn_addr = 0x0, jumpnull = 0, jumpdone = 0}, rowcompare_final = {rctype = 0}, minmax = {values = 0x1700000000, nulls = 0x0, nelems = 0, op = IS_GREATEST, finfo = 0x0, fcinfo_data = 0x0}, fieldselect = {fieldnum = 0, resulttype = 23, argdesc = 0x0}, fieldstore = { fstore = 0x1700000000, argdesc = 0x0, values = 0x0, nulls = 0x0, ncolumns = 0}, sbsref_subscript = {state = 0x1700000000, off = 0, isupper = false, jumpdone = 0}, sbsref = {state = 0x1700000000}, domaincheck = { constraintname = 0x1700000000 , checkvalue = 0x0, checknull = 0x0, resulttype = 0}, convert_rowtype = { convert = 0x1700000000, indesc = 0x0, outdesc = 0x0, map = 0x0, initialized = false}, scalararrayop = {element_type = 0, useOr = 23, typlen = 0, typbyval = false, typalign = 0 '\000', finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0}, xmlexpr = {xexpr = 0x1700000000, named_argvalue = 0x0, named_argnull = 0x0, argvalue = 0x0, argnull = 0x0}, aggref = { astate = 0x1700000000}, grouping_func = {parent = 0x1700000000, clauses = 0x0}, window_func = {wfstate = 0x1700000000}, subplan = {sstate = 0x1700000000}, alternative_subplan = {asstate = 0x1700000000}, agg_deserialize = { aggstate = 0x1700000000, fcinfo_data = 0x0, jumpnull = 0}, agg_strict_input_check = {args = 0x1700000000, nulls = 0x0, nargs = 0, jumpnull = 0}, agg_init_trans = {aggstate = 0x1700000000, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = { aggstate = 0x1700000000, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_trans = {aggstate = 0x1700000000, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0}}}(gdb) p *state$116 = {tag = {type = T_ExprState}, flags = 6 '\006', resnull = false, resvalue = 0, resultslot = 0x0, steps = 0x3069418, evalfunc = 0x6e2d4d , expr = 0x30917a8, evalfunc_private = 0x6e2d4d , steps_len = 5, steps_alloc = 16, parent = 0x3068988, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) n634 FunctionCallInfo fcinfo = op->d.func.fcinfo_data;(gdb) 635 NullableDatum *args = fcinfo->args;(gdb) p *fcinfo$117 = {flinfo = 0x3069830, context = 0x0, resultinfo = 0x0, fncollation = 0, isnull = false, nargs = 2, args = 0x30698a8}(gdb) p *fcinfo->args$118 = {value = 1, isnull = false}(gdb) n640 for (argno = 0; argno < op->d.func.nargs; argno++)(gdb) p op->d.func.nargs$119 = 2(gdb) p *op$120 = {opcode = 7222440, resvalue = 0x3069388, resnull = 0x3069385, d = {fetch = { last_var = 50763824, fixed = false, known_desc = 0x3069888, kind = 0x96c2b2 }, var = {attnum = 50763824, vartype = 0}, wholerow = { var = 0x3069830, first = 136, slow = 152, tupdesc = 0x96c2b2 , junkFilter = 0x2}, assign_var = {resultnum = 50763824, attnum = 0}, assign_tmp = { resultnum = 50763824}, constval = {value = 50763824, isnull = 136}, func = { finfo = 0x3069830, fcinfo_data = 0x3069888, fn_addr = 0x96c2b2 , nargs = 2}, boolexpr = {anynull = 0x3069830, jumpdone = 50763912}, qualexpr = { jumpdone = 50763824}, jump = {jumpdone = 50763824}, nulltest_row = { argdesc = 0x3069830}, param = {paramid = 50763824, paramtype = 0}, cparam = { paramfunc = 0x3069830, paramarg = 0x3069888, paramid = 9880242, paramtype = 0}, casetest = {value = 0x3069830, isnull = 0x3069888}, make_readonly = { value = 0x3069830, isnull = 0x3069888}, iocoerce = {finfo_out = 0x3069830, fcinfo_data_out = 0x3069888, finfo_in = 0x96c2b2 , fcinfo_data_in = 0x2}, sqlvaluefunction = {svf = 0x3069830}, nextvalueexpr = {seqid = 50763824, seqtypid = 0}, arrayexpr = {elemvalues = 0x3069830, elemnulls = 0x3069888, nelems = 9880242, elemtype = 0, elemlength = 2, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x3069830, resultelemtype = 50763912, amstate = 0x96c2b2 }, row = { tupdesc = 0x3069830, elemvalues = 0x3069888, elemnulls = 0x96c2b2 }, rowcompare_step = {finfo = 0x3069830, fcinfo_data = 0x3069888, fn_addr = 0x96c2b2 , jumpnull = 2, jumpdone = 0}, rowcompare_final = { rctype = 50763824}, minmax = {values = 0x3069830, nulls = 0x3069888, nelems = 9880242, op = IS_GREATEST, finfo = 0x2, fcinfo_data = 0x0}, fieldselect = { fieldnum = -26576, resulttype = 0, argdesc = 0x3069888}, fieldstore = { fstore = 0x3069830, argdesc = 0x3069888, values = 0x96c2b2 , nulls = 0x2, ncolumns = 0}, sbsref_subscript = {state = 0x3069830, off = 50763912, isupper = false, jumpdone = 9880242}, sbsref = {state = 0x3069830}, domaincheck = { constraintname = 0x3069830 "\262\302\226", checkvalue = 0x3069888, checknull = 0x96c2b2 , resulttype = 2}, convert_rowtype = { convert = 0x3069830, indesc = 0x3069888, outdesc = 0x96c2b2 , map = 0x2, initialized = false}, scalararrayop = {element_type = 50763824, useOr = false, typlen = 0, typbyval = 136, typalign = -104 '\230', finfo = 0x96c2b2 , fcinfo_data = 0x2, fn_addr = 0x0}, xmlexpr = {xexpr = 0x3069830, named_argvalue = 0x3069888, named_argnull = 0x96c2b2 , argvalue = 0x2, argnull = 0x0}, aggref = {astate = 0x3069830}, grouping_func = {parent = 0x3069830, clauses = 0x3069888}, window_func = {wfstate = 0x3069830}, subplan = { sstate = 0x3069830}, alternative_subplan = {asstate = 0x3069830}, agg_deserialize = {aggstate = 0x3069830, fcinfo_data = 0x3069888, jumpnull = 9880242}, agg_strict_input_check = {args = 0x3069830, nulls = 0x3069888, nargs = 9880242, jumpnull = 0}, agg_init_trans = {aggstate = 0x3069830, pertrans = 0x3069888, aggcontext = 0x96c2b2 , setno = 2, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = {aggstate = 0x3069830, setno = 50763912, transno = 0, setoff = 9880242, jumpnull = 0}, agg_trans = { aggstate = 0x3069830, pertrans = 0x3069888, aggcontext = 0x96c2b2 , setno = 2, transno = 0, setoff = 0}}}(gdb) p op->d->func$121 = {finfo = 0x3069830, fcinfo_data = 0x3069888, fn_addr = 0x96c2b2 , nargs = 2}(gdb) p op->d->func->finfo$122 = (FmgrInfo *) 0x3069830(gdb) p *op->d->func->finfo$123 = {fn_addr = 0x96c2b2 , fn_oid = 65, fn_nargs = 2, fn_strict = true, fn_retset = false, fn_stats = 2 '\002', fn_extra = 0x0, fn_mcxt = 0x3067da0, fn_expr = 0x30917a8}(gdb) p *op->d->func->fcinfo_data$124 = {flinfo = 0x3069830, context = 0x0, resultinfo = 0x0, fncollation = 0, isnull = false, nargs = 2, args = 0x30698a8}(gdb) p *op->d->func->fcinfo_data->flinfo$125 = {fn_addr = 0x96c2b2 , fn_oid = 65, fn_nargs = 2, fn_strict = true, fn_retset = false, fn_stats = 2 '\002', fn_extra = 0x0, fn_mcxt = 0x3067da0, fn_expr = 0x30917a8}(gdb) p *op->d->func->fcinfo_data->args$126 = {value = 1, isnull = false}(gdb) n642 if (args[argno].isnull)(gdb) 640 for (argno = 0; argno < op->d.func.nargs; argno++)(gdb) 642 if (args[argno].isnull)(gdb) 640 for (argno = 0; argno < op->d.func.nargs; argno++)(gdb) 648 fcinfo->isnull = false;(gdb) p *args$127 = {value = 1, isnull = false}(gdb) n649 d = op->d.func.fn_addr(fcinfo);(gdb) 650 *op->resvalue = d;(gdb) p d$128 = 0(gdb) n651 *op->resnull = fcinfo->isnull;(gdb) 654 EEO_NEXT();(gdb) 425 goto out;(gdb) n1747 *isnull = state->resnull;(gdb) 1748 return state->resvalue;(gdb) p *state$129 = {tag = {type = T_ExprState}, flags = 6 '\006', resnull = false, resvalue = 0, resultslot = 0x0, steps = 0x3069418, evalfunc = 0x6e2d4d , expr = 0x30917a8, evalfunc_private = 0x6e2d4d , steps_len = 5, steps_alloc = 16, parent = 0x3068988, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) n1749 }(gdb) ExecEvalExprSwitchContext (state=0x3069380, econtext=0x3068aa0, isNull=0x7ffd184750ef) at ../../../src/include/executor/executor.h:308308 MemoryContextSwitchTo(oldContext);(gdb) 309 return retDatum;(gdb) p retDatum$130 = 0(gdb) n310 }(gdb) 这是第1760次调用
ExecScanSubPlan (node=0x3069268, econtext=0x3068aa0, isNull=0x3068dbd) at nodeSubplan.c:410410 if (subLinkType == ANY_SUBLINK)(gdb) 413 if (rownull)(gdb) 415 else if (DatumGetBool(rowresult))(gdb) p rowresult$131 = 0(gdb) n326 slot = ExecProcNode(planstate))(gdb) 324 for (slot = ExecProcNode(planstate);(gdb) 325 !TupIsNull(slot);(gdb) Breakpoint 17, ExecScanSubPlan (node=0x3069268, econtext=0x3068aa0, isNull=0x3068dbd) at nodeSubplan.c:328328 TupleDesc tdesc = slot->tts_tupleDescriptor;(gdb) 334 if (subLinkType == EXISTS_SUBLINK)(gdb) 341 if (subLinkType == EXPR_SUBLINK)(gdb) 367 if (subLinkType == ARRAY_SUBLINK)(gdb) 383 if (subLinkType == ROWCOMPARE_SUBLINK && found)(gdb) 388 found = true;(gdb) 395 col = 1;(gdb) p *slot->tts_values$132 = 10000000 --> 上一次的数据(gdb) n396 foreach(plst, subplan->paramIds)(gdb) 398 int paramid = lfirst_int(plst);(gdb) 401 prmdata = &(econtext->ecxt_param_exec_vals[paramid]);(gdb) 402 Assert(prmdata->execPlan == NULL);(gdb) p *prmdata$133 = {execPlan = 0x0, value = 10000000, isnull = false}(gdb) n403 prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));(gdb) 404 col++;(gdb) p *prmdata$134 = {execPlan = 0x0, value = 1, isnull = false} --> 本次数据,值为1(gdb) info bNum Type Disp Enb Address What17 breakpoint keep y 0x00000000007303b9 in ExecScanSubPlan at nodeSubplan.c:328 breakpoint already hit 1760 times(gdb) n396 foreach(plst, subplan->paramIds)(gdb) 407 rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext,(gdb) 410 if (subLinkType == ANY_SUBLINK)(gdb) 413 if (rownull)(gdb) 415 else if (DatumGetBool(rowresult))(gdb) 417 result = BoolGetDatum(true);(gdb) 418 *isNull = false;(gdb) 419 break; /* needn't look at any more rows */(gdb) 442 MemoryContextSwitchTo(oldcontext);(gdb) 444 if (subLinkType == ARRAY_SUBLINK)(gdb) 449 else if (!found)(gdb) 464 return result;(gdb) (gdb) p result$135 = 1 --> 满足条件DONE
四、参考资料
N/A
表达式
查询
结果
参数
组合
节点
输出
操作符
数据
状态
语义
一行
情况
上下
上下文
只有
地方
形式
成本
数组
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
关于学生生活的数据库
宜昌我来网络技术有限公司
网吧机房哪个是服务器
嵌入式软件开发架构
软件开发运营分析
自然语言技术神经网络技术
网络安全法律法规2022年最新
北京水性软件开发发展现状
mem 软件开发
法宝数据库和北大法意哪个好
计算机网络技术辅导班
金华电子网络技术哪家好
服务器1u是什么意思
美国最新互联网科技
汕头自主可控软件开发供应商家
软件开发包是什么样子的
我的世界hpy服务器id是多少
斯托克顿生涯数据库
四川行政区划村数据库
怎么进入游戏服务器
局网络安全工作应急演练方案
头条全年数据库报告怎么查
警校的网络安全专业怎么样
软件开发属于工程技术人员吗
如何在我的世界服务器中用材质包
还原数据库sql语句
软件开发产品助理
软件开发部门合理化建议
智能扫地机软件开发
spring数据库密码