溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PostgreSQL 源碼解讀(233)- 查詢#126(NOT IN實現(xiàn)#4)

發(fā)布時間:2020-08-16 21:20:09 來源:ITPUB博客 閱讀:263 作者:husthxd 欄目:關(guān)系型數(shù)據(jù)庫

本節(jié)簡單解釋了PostgreSQL NOT IN在執(zhí)行時為何會出現(xiàn)時快時慢的現(xiàn)象。

測試數(shù)據(jù)如下:

[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表只有一行數(shù)據(jù)(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);

一、數(shù)據(jù)結(jié)構(gòu)

SubPlanState
子計劃運行期狀態(tài)


/* ----------------
 *        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;        /* 組合表達(dá)式狀態(tài);state of combining expression */
    List       *args;            /* 參數(shù)表達(dá)式狀態(tài);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.)
 * 查詢規(guī)劃器在完成子查詢的規(guī)劃后使用SubPlan節(jié)點替換表達(dá)式樹中的SubLink節(jié)點。
 * SubPlan引用了存儲在高層PlannedStmt中的subplans鏈表中的sub-plantree。
 * (避免使用直接鏈接,從而使得拷貝表達(dá)式樹相對比較簡單)
 *
 * 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.
 * 常規(guī)情況下,testexpr指向用于組合操作的可執(zhí)行表達(dá)式(OpExpr、OpExprs的AND/OR樹或者RowCompareExpr);
 * 左參數(shù)是原始的左表達(dá)式,右參數(shù)是PARAM_EXEC參數(shù)節(jié)點用以表示子查詢的輸出。
 * 與原始SubLink節(jié)點的testexpr具有相同的表達(dá)式樹,但PARAM_SUBLINK節(jié)點則使用合適的已編號PARAM_EXEC節(jié)點替代。
 *
 * 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,可執(zhí)行的表達(dá)式是外層plan表達(dá)式樹的一部分。
 * 這種情況下,testexpr為NULL以避免重復(fù)。
 *
 * 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.)
 * 規(guī)劃器還派生了需要傳入和傳出子計劃的值的鏈表。
 * 輸入值標(biāo)識位表達(dá)式的“args”鏈表,在外層查詢上下文中進(jìn)行解析。
 * (這些args通常是Vars,但原則上它們可以是任意表達(dá)式)
 * 這些值以parParam為索引給全局PARAM_EXEC參數(shù)賦值。
 * setParam是PARAM_EXEC參數(shù)鏈表,通過子查詢(如為initplan)計算所得。
 * 它們按子查詢輸出列的位置進(jìn)行排序組織為鏈表形式。
 * (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.
 * 同時,規(guī)劃器計算SubPlan啟動和每次調(diào)用的成本。注意:包括子查詢正常解析testexpr的成本以及哈希表管理成本。
 */
typedef struct SubPlan
{
    Expr        xpr;//表達(dá)式
    /* Fields copied from original SubLink: */
    //從SubLink中拷貝而來
    SubLinkType subLinkType;    /* see above */
    /* The combining operators, transformed to an executable expression: */
    //組合操作符,轉(zhuǎn)換為可執(zhí)行的表達(dá)式
    Node       *testexpr;        /* OpExpr or RowCompareExpr expression tree */
    List       *paramIds;        /* 參數(shù)IDs;IDs of Params embedded in the above */
    /* Identification of the Plan tree to use: */
    //Plan tree標(biāo)識
    int            plan_id;        /* Index (from 1) in PlannedStmt.subplans */
    /* Identification of the SubPlan for EXPLAIN and debugging purposes: */
    //EXPLAIN和debug目的的SubPlan標(biāo)識
    char       *plan_name;        /* A name assigned during planning */
    /* Extra data useful for determining subplan's output type: */
    //用于確定subplan輸出類型的額外信息
    Oid            firstColType;    /* subplan結(jié)果的第一個列類型;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: */
    //執(zhí)行階段的相關(guān)信息
    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: */
    //用于給子查詢傳入和傳出參數(shù)的信息
    /* 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值進(jìn)行傳遞的表達(dá)式;exprs to pass as parParam values */
    /* Estimated execution costs: */
    //估算執(zhí)行成本
    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表示在表達(dá)式中出現(xiàn)的子查詢,在某些情況下組合操作符會出現(xiàn)在SubLink之上。
 *  subLinkType表示表達(dá)式的形式:
 *    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,左操作符是與子查詢目標(biāo)鏈表長度一致的表達(dá)式鏈表。
 * ROWCOMPARE通常有超過一個條目的鏈表;如果子查詢剛好只有一個目標(biāo)列,那么解析器會創(chuàng)建EXPR_SUBLINK
 * (同時所有在子查詢之上的操作符會單獨表示)
 * ROWCOMPARE, EXPR, 和MULTIEXPR要求子查詢至少輸出一行(如返回0行,則結(jié)果為NULL)。
 * ALL,ANY和ROWCOMPARE要求組合操作符輸出布爾型結(jié)果。
 * ALL/ANY使用AND/OR語義來組合每一行的結(jié)果。
 *
 * 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節(jié)點,但實際上并不是可執(zhí)行的,必須在計劃階段通過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只包含左表達(dá)式的裸形式,operName是組合操作符的字符串名稱。
 * 同時,子查詢是裸parsetree。在解析分析期間,
 * 解析器轉(zhuǎn)換testexpr為完整的布爾表達(dá)式用于比較左操作符值與PARAM_SUBLINK節(jié)點所代表的子查詢輸出列值。
 * 子查詢會轉(zhuǎn)換為Query結(jié)構(gòu)體。
 * 在已存儲的規(guī)則和重寫時可見的表示形式。
 *
 * 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當(dāng)前只用于MULTIEXPR,在其他SubLinks中取值為0.
 * 該數(shù)字標(biāo)識了在UPDATE語句SET鏈表中不同的多個賦值子查詢。
 * 只有在特定的targetlist內(nèi)是唯一的。
 * 出現(xiàn)在tlist其他地方的PARAM_MULTIEXPR參數(shù)依賴于MULTIEXPR的輸出列。
 *
 * The CTE_SUBLINK case never occurs in actual SubLink nodes, but it is used
 * in SubPlans generated for WITH subqueries.
 * CTE_SUBLINK不會出現(xiàn)在實際的SubLink節(jié)點中,但用于WITH子查詢所產(chǎn)生的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結(jié)構(gòu)體


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狀態(tài)

/* ----------------
 *     MaterialState information
 *
 *        materialize nodes are used to materialize the results
 *        of a subplan into a temporary file.
 *        materialize節(jié)點用于物化subplan的結(jié)果為臨時文件。
 *
 *        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標(biāo)記;capability flags to pass to tuplestore */
    bool        eof_underlying; /* 已經(jīng)到達(dá)underlying plan的末尾?reached end of underlying plan? */
    Tuplestorestate *tuplestorestate;
} MaterialState;

Tuplestorestate
Tuplestore相關(guān)操作的私有狀態(tài)。


/*
 * 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;        /* 狀態(tài)枚舉值;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在實際執(zhí)行時會轉(zhuǎn)換為ALL_SUBLINK,執(zhí)行的快慢取決于什么時候會碰到符合條件的記錄,一旦遇到馬上返回。因此,SQL的執(zhí)行時間與數(shù)據(jù)表的掃描順序有緊密的關(guān)系,符合條件的行越早出現(xiàn),程序越早返回,需要的時間越短。
相關(guān)代碼如下:

...
        //解析表達(dá)式
        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表達(dá)式測試到其中一行滿足條件,那么就會馬上退出循環(huán),而這一行如果非常幸運的出現(xiàn)在掃描的最開始的地方,那執(zhí)行時間將會很快(掃描幾個數(shù)據(jù)塊 vs 全表掃描)。

新建一張表,插入2條記錄,其中id = 2的行出現(xiàn)在block編號最小的地方,而id = 1出現(xiàn)在block編號最大的地方,這時候pg就會出現(xiàn)時快時慢的情況,兩者相差3個數(shù)量級。

[local]:5432 pg12@testdb=# create table tbl3(id int);
CREATE TABLE
Time: 1.852 ms
[local]:5432 pg12@testdb=# insert into tbl3 values(1);
INSERT 0 1
Time: 1.276 ms
[local]:5432 pg12@testdb=# insert into tbl3 values(2);
INSERT 0 1
Time: 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
 * 默認(rèn)情況下每次都不得不重新掃描subplan
 */
static Datum
ExecScanSubPlan(SubPlanState *node,
                ExprContext *econtext,
                bool *isNull)
{
    SubPlan    *subplan = node->subplan;//子計劃
    PlanState  *planstate = node->planstate;//計劃運行期狀態(tài)
    SubLinkType subLinkType = subplan->subLinkType;//子鏈接類型
    MemoryContext oldcontext;//原內(nèi)存上下文
    TupleTableSlot *slot;//元組slot
    Datum        result;//結(jié)果指針
    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 */
    //數(shù)組
    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.)
     */
    //通過父計劃相關(guān)值中設(shè)置子計劃參數(shù)
    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.
     */
    //執(zhí)行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,結(jié)果是布爾值(組合運算符的結(jié)果).
     * PG通過跨元組(如子計劃產(chǎn)生多個元組)合并結(jié)果,對于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產(chǎn)生不超過一個元組,否則報錯.如果沒有元組產(chǎn)生,返回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產(chǎn)生任意數(shù)目的元組,使用第一個列值組成數(shù)組.
     * 特別注意的是如沒有元組產(chǎn)生則產(chǎn)生0個元素的數(shù)組(8.3以前是返回NULL).
     */
    result = BoolGetDatum(subLinkType == ALL_SUBLINK);//ALL為T,否則為F
    *isNull = false;
    for (slot = ExecProcNode(planstate);
         !TupIsNull(slot);
         slot = ExecProcNode(planstate))//循環(huán)獲取元組,直至沒有元組為NULL(即已完成)
    {
        //元組描述符
        TupleDesc    tdesc = slot->tts_tupleDescriptor;
        Datum        rowresult;//結(jié)果
        bool        rownull;//是否為空?
        int            col;//列計數(shù)器
        ListCell   *plst;//臨時變量
        if (subLinkType == EXISTS_SUBLINK)//EXISTS
        {
            found = true;
            result = BoolGetDatum(true);
            break;
        }
        if (subLinkType == EXPR_SUBLINK)//EXPR表達(dá)式
        {
            /* 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)//數(shù)組
        {
            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,并解析組合表達(dá)式
         */
        col = 1;//列從1計數(shù)
        foreach(plst, subplan->paramIds)//循環(huán)遍歷子查詢參數(shù)
        {
            int            paramid = lfirst_int(plst);
            ParamExecData *prmdata;
            prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
            Assert(prmdata->execPlan == NULL);
            //獲取參數(shù)值
            prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
            //下一個列
            col++;
        }
        //解析表達(dá)式
        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 */
        //在調(diào)用者上下文中返回結(jié)果
        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沒有結(jié)果返回,設(shè)置result&isNull值
         */
        if (subLinkType == EXPR_SUBLINK ||
            subLinkType == ROWCOMPARE_SUBLINK)
        {
            result = (Datum) 0;
            *isNull = true;
        }
    }
    //返回結(jié)果
    return result;
}

三、跟蹤分析

執(zhí)行SQL:

[local]:5432 pg12@testdb=# select * from tbl a where a.id not in (select b.id from t_big_null b);

啟動gdb跟蹤,設(shè)置斷點,觀察到斷點hit 1760次后就會退出,因此設(shè)置為忽略前1758次,只跟蹤最后2次。

(gdb) info b
Num     Type           Disp Enb Address            What
13      breakpoint     keep y   0x0000000000721126 in ExecMaterial at nodeMaterial.c:150
    breakpoint already hit 1760 times
    ignore next 3360 hits
...
(gdb) b nodeSubplan.c:328
Breakpoint 17 at 0x7303b9: file nodeSubplan.c, line 328.
(gdb) del 16
(gdb) info b
Num     Type           Disp Enb Address            What
17      breakpoint     keep y   0x00000000007303b9 in ExecScanSubPlan at nodeSubplan.c:328
(gdb) ignore 17 1758
Will ignore next 1758 crossings of breakpoint 17.
(gdb) c
Continuing.

開始跟蹤,這是第1759次,這時候從SubPlan獲取的數(shù)據(jù)是id = 10000000

Breakpoint 17, ExecScanSubPlan (node=0x3069268, econtext=0x3068aa0, isNull=0x3068dbd)
    at nodeSubplan.c:328
328            TupleDesc    tdesc = slot->tts_tupleDescriptor;
(gdb) n
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) 
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) n
404                col++;
(gdb) p *prmdata
$110 = {execPlan = 0x0, value = 10000000, isnull = false}
(gdb) n
396            foreach(plst, subplan->paramIds)
(gdb)

解析表達(dá)式

407            rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext,
(gdb) step
ExecEvalExprSwitchContext (state=0x3069380, econtext=0x3068aa0, isNull=0x7ffd184750ef)
    at ../../../src/include/executor/executor.h:306
306        oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
(gdb) n
307        retDatum = state->evalfunc(state, econtext, isNull);
(gdb) step
ExecInterpExpr (state=0x3069380, econtext=0x3068aa0, isnull=0x7ffd184750ef)
    at execExprInterp.c:404
404        if (unlikely(state == NULL))
(gdb) n
411        op = state->steps;
(gdb) p *state
$111 = {tag = {type = T_ExprState}, flags = 6 '\006', resnull = false, resvalue = 0, 
  resultslot = 0x0, steps = 0x3069418, evalfunc = 0x6e2d4d <ExecInterpExpr>, 
  expr = 0x30917a8, evalfunc_private = 0x6e2d4d <ExecInterpExpr>, steps_len = 5, 
  steps_alloc = 16, parent = 0x3068988, ext_params = 0x0, innermost_caseval = 0x0, 
  innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}
(gdb) n
412        resultslot = state->resultslot;
(gdb) 
413        innerslot = econtext->ecxt_innertuple;
(gdb) 
414        outerslot = econtext->ecxt_outertuple;
(gdb) 
415        scanslot = econtext->ecxt_scantuple;
(gdb) p *innerslot
Cannot access memory at address 0x0
(gdb) p *outerslot
Cannot access memory at address 0x0
(gdb) n
418        EEO_DISPATCH();
(gdb) p *scanslot
$112 = {type = T_TupleTableSlot, tts_flags = 16, tts_nvalid = 1, 
  tts_ops = 0xc3e780 <TTSOpsBufferHeapTuple>, 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) n
448                CheckOpSlotCompatibility(op, scanslot);
(gdb) n
450                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) n
962                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 <Address 0x1700000000 out of bounds>, 
      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 <ExecInterpExpr>, 
  expr = 0x30917a8, evalfunc_private = 0x6e2d4d <ExecInterpExpr>, steps_len = 5, 
  steps_alloc = 16, parent = 0x3068988, ext_params = 0x0, innermost_caseval = 0x0, 
  innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}
(gdb) n
634                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) n
640                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 <int4eq>}, var = {attnum = 50763824, vartype = 0}, wholerow = {
      var = 0x3069830, first = 136, slow = 152, tupdesc = 0x96c2b2 <int4eq>, 
      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 <int4eq>, 
      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 <int4eq>, 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 <int4eq>}, row = {
      tupdesc = 0x3069830, elemvalues = 0x3069888, elemnulls = 0x96c2b2 <int4eq>}, 
    rowcompare_step = {finfo = 0x3069830, fcinfo_data = 0x3069888, 
      fn_addr = 0x96c2b2 <int4eq>, 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 <int4eq>, 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 <int4eq>, resulttype = 2}, convert_rowtype = {
      convert = 0x3069830, indesc = 0x3069888, outdesc = 0x96c2b2 <int4eq>, map = 0x2, 
      initialized = false}, scalararrayop = {element_type = 50763824, useOr = false, 
      typlen = 0, typbyval = 136, typalign = -104 '\230', finfo = 0x96c2b2 <int4eq>, 
      fcinfo_data = 0x2, fn_addr = 0x0}, xmlexpr = {xexpr = 0x3069830, 
      named_argvalue = 0x3069888, named_argnull = 0x96c2b2 <int4eq>, 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 <int4eq>, 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 <int4eq>, 
      setno = 2, transno = 0, setoff = 0}}}
(gdb) p op->d->func
$121 = {finfo = 0x3069830, fcinfo_data = 0x3069888, fn_addr = 0x96c2b2 <int4eq>, 
  nargs = 2}
(gdb) p op->d->func->finfo
$122 = (FmgrInfo *) 0x3069830
(gdb) p *op->d->func->finfo
$123 = {fn_addr = 0x96c2b2 <int4eq>, 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 <int4eq>, 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) n
642                    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) n
649                d = op->d.func.fn_addr(fcinfo);
(gdb) 
650                *op->resvalue = d;
(gdb) p d
$128 = 0
(gdb) n
651                *op->resnull = fcinfo->isnull;
(gdb) 
654                EEO_NEXT();
(gdb) 
425                goto out;
(gdb) n
1747        *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 <ExecInterpExpr>, 
  expr = 0x30917a8, evalfunc_private = 0x6e2d4d <ExecInterpExpr>, steps_len = 5, 
  steps_alloc = 16, parent = 0x3068988, ext_params = 0x0, innermost_caseval = 0x0, 
  innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}
(gdb) n
1749    }
(gdb) 
ExecEvalExprSwitchContext (state=0x3069380, econtext=0x3068aa0, isNull=0x7ffd184750ef)
    at ../../../src/include/executor/executor.h:308
308        MemoryContextSwitchTo(oldContext);
(gdb) 
309        return retDatum;
(gdb) p retDatum
$130 = 0
(gdb) n
310    }
(gdb)

這是第1760次調(diào)用

ExecScanSubPlan (node=0x3069268, econtext=0x3068aa0, isNull=0x3068dbd)
    at nodeSubplan.c:410
410            if (subLinkType == ANY_SUBLINK)
(gdb) 
413                if (rownull)
(gdb) 
415                else if (DatumGetBool(rowresult))
(gdb) p rowresult
$131 = 0
(gdb) n
326             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:328
328            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 --> 上一次的數(shù)據(jù)
(gdb) n
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) p *prmdata
$133 = {execPlan = 0x0, value = 10000000, isnull = false}
(gdb) n
403                prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
(gdb) 
404                col++;
(gdb) p *prmdata
$134 = {execPlan = 0x0, value = 1, isnull = false} --> 本次數(shù)據(jù),值為1
(gdb) info b
Num     Type           Disp Enb Address            What
17      breakpoint     keep y   0x00000000007303b9 in ExecScanSubPlan at nodeSubplan.c:328
    breakpoint already hit 1760 times
(gdb) n
396            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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI