溫馨提示×

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

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

PostgreSQL中?ExecutePlan函數(shù)與ExecSeqScan函數(shù)的作用是什么

發(fā)布時(shí)間:2021-11-10 15:37:39 來(lái)源:億速云 閱讀:124 作者:iii 欄目:關(guān)系型數(shù)據(jù)庫(kù)

這篇文章主要介紹“PostgreSQL中ExecutePlan函數(shù)與ExecSeqScan函數(shù)的作用是什么”,在日常操作中,相信很多人在PostgreSQL中ExecutePlan函數(shù)與ExecSeqScan函數(shù)的作用是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”PostgreSQL中ExecutePlan函數(shù)與ExecSeqScan函數(shù)的作用是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

ExecutePlan函數(shù)處理查詢計(jì)劃,直到檢索到指定數(shù)量(參數(shù)numbertuple)的元組,并沿著指定的方向掃描。ExecSeqScan函數(shù)順序掃描relation,返回下一個(gè)符合條件的元組。

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

Plan
所有計(jì)劃節(jié)點(diǎn)通過(guò)將Plan結(jié)構(gòu)作為第一個(gè)字段從Plan結(jié)構(gòu)“派生”。這確保了在將節(jié)點(diǎn)轉(zhuǎn)換為計(jì)劃節(jié)點(diǎn)時(shí),一切都能正常工作。(在執(zhí)行器中以通用方式傳遞時(shí),節(jié)點(diǎn)指針經(jīng)常被轉(zhuǎn)換為Plan *)

/* ----------------
 *      Plan node
 *
 * All plan nodes "derive" from the Plan structure by having the
 * Plan structure as the first field.  This ensures that everything works
 * when nodes are cast to Plan's.  (node pointers are frequently cast to Plan*
 * when passed around generically in the executor)
 * 所有計(jì)劃節(jié)點(diǎn)通過(guò)將Plan結(jié)構(gòu)作為第一個(gè)字段從Plan結(jié)構(gòu)“派生”。
 * 這確保了在將節(jié)點(diǎn)轉(zhuǎn)換為計(jì)劃節(jié)點(diǎn)時(shí),一切都能正常工作。
 * (在執(zhí)行器中以通用方式傳遞時(shí),節(jié)點(diǎn)指針經(jīng)常被轉(zhuǎn)換為Plan *)
 *
 * We never actually instantiate any Plan nodes; this is just the common
 * abstract superclass for all Plan-type nodes.
 * 從未實(shí)例化任何Plan節(jié)點(diǎn);這只是所有Plan-type節(jié)點(diǎn)的通用抽象超類。
 * ----------------
 */
typedef struct Plan
{
    NodeTag     type;//節(jié)點(diǎn)類型

    /*
     * 成本估算信息;estimated execution costs for plan (see costsize.c for more info)
     */
    Cost        startup_cost;   /* 啟動(dòng)成本;cost expended before fetching any tuples */
    Cost        total_cost;     /* 總成本;total cost (assuming all tuples fetched) */

    /*
     * 優(yōu)化器估算信息;planner's estimate of result size of this plan step
     */
    double      plan_rows;      /* 行數(shù);number of rows plan is expected to emit */
    int         plan_width;     /* 平均行大小(Byte為單位);average row width in bytes */

    /*
     * 并行執(zhí)行相關(guān)的信息;information needed for parallel query
     */
    bool        parallel_aware; /* 是否參與并行執(zhí)行邏輯?engage parallel-aware logic? */
    bool        parallel_safe;  /* 是否并行安全;OK to use as part of parallel plan? */

    /*
     * Plan類型節(jié)點(diǎn)通用的信息.Common structural data for all Plan types.
     */
    int         plan_node_id;   /* unique across entire final plan tree */
    List       *targetlist;     /* target list to be computed at this node */
    List       *qual;           /* implicitly-ANDed qual conditions */
    struct Plan *lefttree;      /* input plan tree(s) */
    struct Plan *righttree;
    List       *initPlan;       /* Init Plan nodes (un-correlated expr
                                 * subselects) */

    /*
     * Information for management of parameter-change-driven rescanning
     * parameter-change-driven重掃描的管理信息.
     * 
     * extParam includes the paramIDs of all external PARAM_EXEC params
     * affecting this plan node or its children.  setParam params from the
     * node's initPlans are not included, but their extParams are.
     *
     * allParam includes all the extParam paramIDs, plus the IDs of local
     * params that affect the node (i.e., the setParams of its initplans).
     * These are _all_ the PARAM_EXEC params that affect this node.
     */
    Bitmapset  *extParam;
    Bitmapset  *allParam;
} Plan;

二、源碼解讀

ExecutePlan
PortalRunSelect->ExecutorRun->ExecutePlan函數(shù)處理查詢計(jì)劃,直到檢索到指定數(shù)量(參數(shù)numbertuple)的元組,并沿著指定的方向掃描.

/* ----------------------------------------------------------------
 *      ExecutePlan
 *
 *      Processes the query plan until we have retrieved 'numberTuples' tuples,
 *      moving in the specified direction.
 *      處理查詢計(jì)劃,直到檢索到指定數(shù)量(參數(shù)numbertuple)的元組,并沿著指定的方向移動(dòng)。
 *
 *      Runs to completion if numberTuples is 0
 *      如參數(shù)numbertuple為0,則運(yùn)行至結(jié)束為止
 *
 * Note: the ctid attribute is a 'junk' attribute that is removed before the
 * user can see it
 * 注意:ctid屬性是"junk"屬性,在返回給用戶前會(huì)移除
 * ----------------------------------------------------------------
 */
static void
ExecutePlan(EState *estate,//執(zhí)行狀態(tài)
            PlanState *planstate,//計(jì)劃狀態(tài)
            bool use_parallel_mode,//是否使用并行模式
            CmdType operation,//操作類型
            bool sendTuples,//是否需要傳輸元組
            uint64 numberTuples,//元組數(shù)量
            ScanDirection direction,//掃描方向
            DestReceiver *dest,//接收的目標(biāo)端
            bool execute_once)//是否只執(zhí)行一次
{
    TupleTableSlot *slot;//元組表Slot
    uint64      current_tuple_count;//當(dāng)前的元組計(jì)數(shù)

    /*
     * initialize local variables
     * 初始化本地變量
     */
    current_tuple_count = 0;

    /*
     * Set the direction.
     * 設(shè)置掃描方向
     */
    estate->es_direction = direction;

    /*
     * If the plan might potentially be executed multiple times, we must force
     * it to run without parallelism, because we might exit early.
     * 如果計(jì)劃可能被多次執(zhí)行,那么必須強(qiáng)制它在非并行的情況下運(yùn)行,因?yàn)榭赡軙?huì)提前退出。
     */
    if (!execute_once)
        use_parallel_mode = false;//如需多次執(zhí)行,則不允許并行執(zhí)行

    estate->es_use_parallel_mode = use_parallel_mode;
    if (use_parallel_mode)
        EnterParallelMode();//如并行,則進(jìn)入并行模式

    /*
     * Loop until we've processed the proper number of tuples from the plan.
     * 循環(huán)直至執(zhí)行計(jì)劃已處理完成相應(yīng)數(shù)量的元組
     * 注意:每次循環(huán)只處理一個(gè)元組,每次都要重置元組Expr的上下文/過(guò)濾不需要的列/發(fā)送元組
     */
    for (;;)
    {
        /* Reset the per-output-tuple exprcontext */
        //重置Expr上下文
        ResetPerTupleExprContext(estate);

        /*
         * Execute the plan and obtain a tuple
         * 執(zhí)行計(jì)劃,獲取一個(gè)元組
         */
        slot = ExecProcNode(planstate);

        /*
         * if the tuple is null, then we assume there is nothing more to
         * process so we just end the loop...
         * 如果返回的元組為空,那么可以認(rèn)為沒(méi)有什么要處理的了,結(jié)束循環(huán)……
         */
        if (TupIsNull(slot))
        {
            /*
             * If we know we won't need to back up, we can release resources
             * at this point.
             * 如果已知不需要備份(回溯),那么可以釋放資源了
             */
            if (!(estate->es_top_eflags & EXEC_FLAG_BACKWARD))
                (void) ExecShutdownNode(planstate);
            break;
        }

        /*
         * If we have a junk filter, then project a new tuple with the junk
         * removed.
         * 如有junk過(guò)濾器,使用junk執(zhí)行投影操作,產(chǎn)生一個(gè)新的元組
         * 
         * Store this new "clean" tuple in the junkfilter's resultSlot.
         * (Formerly, we stored it back over the "dirty" tuple, which is WRONG
         * because that tuple slot has the wrong descriptor.)
         * 將這個(gè)新的“clean”元組存儲(chǔ)在junkfilter的resultSlot中。
         * (以前,將其存儲(chǔ)在“dirty” tuple上,這是錯(cuò)誤的,因?yàn)樵搕uple slot的描述符是錯(cuò)誤的。)
         */
        if (estate->es_junkFilter != NULL)
            slot = ExecFilterJunk(estate->es_junkFilter, slot);

        /*
         * If we are supposed to send the tuple somewhere, do so. (In
         * practice, this is probably always the case at this point.)
         * 如果要將元組發(fā)送到某個(gè)地方(接收器),那么就這樣做。
         * (實(shí)際上,在這一點(diǎn)上可能總是如此。)
         */
        if (sendTuples)
        {
            /*
             * If we are not able to send the tuple, we assume the destination
             * has closed and no more tuples can be sent. If that's the case,
             * end the loop.
             * 如果不能發(fā)送元組,有理由假設(shè)目的接收器已經(jīng)關(guān)閉,不能發(fā)送更多元組,結(jié)束循環(huán)。
             */
            if (!dest->receiveSlot(slot, dest))
                break;//跳出循環(huán)
        }

        /*
         * Count tuples processed, if this is a SELECT.  (For other operation
         * types, the ModifyTable plan node must count the appropriate
         * events.)
         * 如果操作類型為CMD_SELECT,則計(jì)算已處理的元組。
         * (對(duì)于其他操作類型,ModifyTable plan節(jié)點(diǎn)必須統(tǒng)計(jì)合適的事件。)
         */
        if (operation == CMD_SELECT)
            (estate->es_processed)++;

        /*
         * check our tuple count.. if we've processed the proper number then
         * quit, else loop again and process more tuples.  Zero numberTuples
         * means no limit.
         * 檢查處理的元組計(jì)數(shù)…
         * 如果已完成處理,那么退出,否則再次循環(huán)并處理更多元組。
         * 注意:numberTuples=0表示沒(méi)有限制。
         */
        current_tuple_count++;
        if (numberTuples && numberTuples == current_tuple_count)
        {
            /*
             * If we know we won't need to back up, we can release resources
             * at this point.
             * 不需要回溯,可以在此時(shí)釋放資源。
             */
            if (!(estate->es_top_eflags & EXEC_FLAG_BACKWARD))
                (void) ExecShutdownNode(planstate);
            break;
        }
    }

    if (use_parallel_mode)
        ExitParallelMode();//退出并行模式
}


/* ----------------------------------------------------------------
 *      ExecProcNode
 *
 *      Execute the given node to return a(nother) tuple.
 *      調(diào)用node->ExecProcNode函數(shù)返回元組(one or another)
 * ----------------------------------------------------------------
 */
#ifndef FRONTEND
static inline TupleTableSlot *
ExecProcNode(PlanState *node)
{
    if (node->chgParam != NULL) /* 參數(shù)變化?something changed? */
        ExecReScan(node);       /* 調(diào)用ExecReScan函數(shù);let ReScan handle this */

    return node->ExecProcNode(node);//執(zhí)行ExecProcNode
}
#endif

ExecSeqScan
ExecSeqScan函數(shù)順序掃描relation,返回下一個(gè)符合條件的元組。

/* ----------------------------------------------------------------
 *      ExecSeqScan(node)
 *      
 *      Scans the relation sequentially and returns the next qualifying
 *      tuple.
 *      We call the ExecScan() routine and pass it the appropriate
 *      access method functions.
 *      順序掃描relation,返回下一個(gè)符合條件的元組。
 *      調(diào)用ExecScan函數(shù),傳入相應(yīng)的訪問(wèn)方法函數(shù)
 * ----------------------------------------------------------------
 */
static TupleTableSlot *
ExecSeqScan(PlanState *pstate)
{
    SeqScanState *node = castNode(SeqScanState, pstate);//獲取SeqScanState

    return ExecScan(&node->ss,
                    (ExecScanAccessMtd) SeqNext,
                    (ExecScanRecheckMtd) SeqRecheck);//執(zhí)行Scan
}

/* ----------------------------------------------------------------
 *      ExecScan
 *
 *      Scans the relation using the 'access method' indicated and
 *      returns the next qualifying tuple in the direction specified
 *      in the global variable ExecDirection.
 *      The access method returns the next tuple and ExecScan() is
 *      responsible for checking the tuple returned against the qual-clause.
 *      使用指定的“訪問(wèn)方法”掃描關(guān)系,并按照全局變量ExecDirection中指定的方向返回下一個(gè)符合條件的元組。
 *      訪問(wèn)方法返回下一個(gè)元組,ExecScan()負(fù)責(zé)根據(jù)qual-clause條件子句檢查返回的元組是否符合條件。
 *
 *      A 'recheck method' must also be provided that can check an
 *      arbitrary tuple of the relation against any qual conditions
 *      that are implemented internal to the access method.
 *      調(diào)用者還必須提供“recheck method”,根據(jù)訪問(wèn)方法內(nèi)部實(shí)現(xiàn)的條件檢查關(guān)系的所有元組。
 *
 *      Conditions:
 *        -- the "cursor" maintained by the AMI is positioned at the tuple
 *           returned previously.
 *      前提條件:
 *        由AMI負(fù)責(zé)維護(hù)的游標(biāo)已由先前的處理過(guò)程定位.
 *
 *      Initial States:
 *        -- the relation indicated is opened for scanning so that the
 *           "cursor" is positioned before the first qualifying tuple.
 *      初始狀態(tài):
 *        在游標(biāo)可定位返回第一個(gè)符合條件的元組前,relation已打開(kāi)可進(jìn)行掃描
 * ----------------------------------------------------------------
 */
TupleTableSlot *
ExecScan(ScanState *node,
         ExecScanAccessMtd accessMtd,   /* 返回元組的訪問(wèn)方法;function returning a tuple */
         ExecScanRecheckMtd recheckMtd) //recheck方法
{
    ExprContext *econtext;//表達(dá)式上下文
    ExprState  *qual;//表達(dá)式狀態(tài)
    ProjectionInfo *projInfo;//投影信息

    /*
     * Fetch data from node
     * 從node中提取數(shù)據(jù)
     */
    qual = node->ps.qual;
    projInfo = node->ps.ps_ProjInfo;
    econtext = node->ps.ps_ExprContext;

    /* interrupt checks are in ExecScanFetch */
    //在ExecScanFetch中有中斷檢查

    /*
     * If we have neither a qual to check nor a projection to do, just skip
     * all the overhead and return the raw scan tuple.
     * 如果既沒(méi)有要檢查的條件qual,也沒(méi)有要做的投影操作,那么就跳過(guò)所有的操作并返回raw scan元組。
     */
    if (!qual && !projInfo)
    {
        ResetExprContext(econtext);
        return ExecScanFetch(node, accessMtd, recheckMtd);
    }

    /*
     * Reset per-tuple memory context to free any expression evaluation
     * storage allocated in the previous tuple cycle.
     * 重置每個(gè)元組內(nèi)存上下文,以釋放用于在前一個(gè)元組循環(huán)中分配的表達(dá)式求值內(nèi)存空間。
     */
    ResetExprContext(econtext);

    /*
     * get a tuple from the access method.  Loop until we obtain a tuple that
     * passes the qualification.
     * 從訪問(wèn)方法中獲取一個(gè)元組。循環(huán),直到獲得通過(guò)限定條件的元組。
     */
    for (;;)
    {
        TupleTableSlot *slot;//slot變量

        slot = ExecScanFetch(node, accessMtd, recheckMtd);//獲取slot

        /*
         * if the slot returned by the accessMtd contains NULL, then it means
         * there is nothing more to scan so we just return an empty slot,
         * being careful to use the projection result slot so it has correct
         * tupleDesc.
         * 如果accessMtd方法返回的slot中包含NULL,那么這意味著不再需要掃描了,
         * 這時(shí)候只需要返回一個(gè)空slot,小心使用投影結(jié)果slot,這樣可以有正確的tupleDesc了。
         */
        if (TupIsNull(slot))
        {
            if (projInfo)
                return ExecClearTuple(projInfo->pi_state.resultslot);
            else
                return slot;
        }

        /*
         * place the current tuple into the expr context
         * 把當(dāng)前tuple放入到expr上下文中
         */
        econtext->ecxt_scantuple = slot;

        /*
         * check that the current tuple satisfies the qual-clause
         * 檢查當(dāng)前的tuple是否符合qual-clause條件
         * 
         * check for non-null qual here to avoid a function call to ExecQual()
         * when the qual is null ... saves only a few cycles, but they add up
         * ...
         * 在這里檢查qual是否非空,以避免在qual為空時(shí)調(diào)用ExecQual()函數(shù)…
         * 只節(jié)省了幾個(gè)調(diào)用周期,但它們加起來(lái)……的成本還是蠻可觀的
         */
        if (qual == NULL || ExecQual(qual, econtext))
        {
            /*
             * Found a satisfactory scan tuple.
             * 發(fā)現(xiàn)一個(gè)滿足條件的元組
             */
            if (projInfo)
            {
                /*
                 * Form a projection tuple, store it in the result tuple slot
                 * and return it.
                 * 構(gòu)造一個(gè)投影元組,存儲(chǔ)在結(jié)果元組slot中并返回
                 */
                return ExecProject(projInfo);//執(zhí)行投影操作并返回
            }
            else
            {
                /*
                 * Here, we aren't projecting, so just return scan tuple.
                 * 不需要執(zhí)行投影操作,返回元組
                 */
                return slot;//直接返回
            }
        }
        else
            InstrCountFiltered1(node, 1);//instrument計(jì)數(shù)

        /*
         * Tuple fails qual, so free per-tuple memory and try again.
         * 元組不滿足條件,釋放資源,重試
         */
        ResetExprContext(econtext);
    }
}


/*
 * ExecScanFetch -- check interrupts & fetch next potential tuple
 * ExecScanFetch -- 檢查中斷&提前下一個(gè)備選元組
 *
 * This routine is concerned with substituting a test tuple if we are
 * inside an EvalPlanQual recheck.  If we aren't, just execute
 * the access method's next-tuple routine.
 * 這個(gè)例程是處理測(cè)試元組的替換(如果在EvalPlanQual重新檢查中)。
 * 如果不是在EvalPlanQual中,則執(zhí)行access方法的next-tuple例程。
 */
static inline TupleTableSlot *
ExecScanFetch(ScanState *node,
              ExecScanAccessMtd accessMtd,
              ExecScanRecheckMtd recheckMtd)
{
    EState     *estate = node->ps.state;

    CHECK_FOR_INTERRUPTS();//檢查中斷

    if (estate->es_epqTuple != NULL)//如es_epqTuple不為NULL()
    {
        //es_epqTuple字段用于在READ COMMITTED模式中替換更新后的元組后,重新評(píng)估是否滿足執(zhí)行計(jì)劃的條件quals
        /*
         * We are inside an EvalPlanQual recheck.  Return the test tuple if
         * one is available, after rechecking any access-method-specific
         * conditions.
         * 我們正在EvalPlanQual復(fù)查。
         * 如果test tuple可用,則在重新檢查所有特定于訪問(wèn)方法的條件后返回該元組。
         */
        Index       scanrelid = ((Scan *) node->ps.plan)->scanrelid;//訪問(wèn)的relid

        if (scanrelid == 0)//relid==0
        {
            TupleTableSlot *slot = node->ss_ScanTupleSlot;

            /*
             * This is a ForeignScan or CustomScan which has pushed down a
             * join to the remote side.  The recheck method is responsible not
             * only for rechecking the scan/join quals but also for storing
             * the correct tuple in the slot.
             * 這是一個(gè)ForeignScan或CustomScan,它將下推到遠(yuǎn)程端。
             * recheck方法不僅負(fù)責(zé)重新檢查掃描/連接quals,還負(fù)責(zé)在slot中存儲(chǔ)正確的元組。
             */
            if (!(*recheckMtd) (node, slot))
                ExecClearTuple(slot);   /* 驗(yàn)證不通過(guò),釋放資源,不返回元組;would not be returned by scan */
            return slot;
        }
        else if (estate->es_epqTupleSet[scanrelid - 1])//從estate->es_epqTupleSet數(shù)組中獲取標(biāo)志
        {
            TupleTableSlot *slot = node->ss_ScanTupleSlot;//獲取slot

            /* Return empty slot if we already returned a tuple */
            //如已返回元組,則清空slot
            if (estate->es_epqScanDone[scanrelid - 1])
                return ExecClearTuple(slot);
            /* Else mark to remember that we shouldn't return more */
            //否則,標(biāo)記沒(méi)有返回
            estate->es_epqScanDone[scanrelid - 1] = true;

            /* Return empty slot if we haven't got a test tuple */
            //如test tuple為NULL,則清空slot
            if (estate->es_epqTuple[scanrelid - 1] == NULL)
                return ExecClearTuple(slot);

            /* Store test tuple in the plan node's scan slot */
            //在計(jì)劃節(jié)點(diǎn)的scan slot中存儲(chǔ)test tuple
            ExecStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
                               slot, false);

            /* Check if it meets the access-method conditions */
            //檢查是否滿足訪問(wèn)方法條件
            if (!(*recheckMtd) (node, slot))
                ExecClearTuple(slot);   /* 不滿足,清空slot;would not be returned by scan */

            return slot;
        }
    }

    /*
     * Run the node-type-specific access method function to get the next tuple
     * 運(yùn)行node-type-specific方法函數(shù),獲取下一個(gè)tuple
     */
    return (*accessMtd) (node);
}


/*
 * ExecProject
 *
 * Projects a tuple based on projection info and stores it in the slot passed
 * to ExecBuildProjectInfo().
 * 根據(jù)投影信息投影一個(gè)元組,并將其存儲(chǔ)在傳遞給ExecBuildProjectInfo()的slot中。
 *
 * Note: the result is always a virtual tuple; therefore it may reference
 * the contents of the exprContext's scan tuples and/or temporary results
 * constructed in the exprContext.  If the caller wishes the result to be
 * valid longer than that data will be valid, he must call ExecMaterializeSlot
 * on the result slot.
 * 注意:結(jié)果總是一個(gè)虛擬元組;
 * 因此,它可以引用exprContext的掃描元組和/或exprContext中構(gòu)造的臨時(shí)結(jié)果的內(nèi)容。
 * 如果調(diào)用者希望結(jié)果有效的時(shí)間長(zhǎng)于數(shù)據(jù)有效的時(shí)間,必須在結(jié)果slot上調(diào)用ExecMaterializeSlot。
 */
#ifndef FRONTEND
static inline TupleTableSlot *
ExecProject(ProjectionInfo *projInfo)
{
    ExprContext *econtext = projInfo->pi_exprContext;
    ExprState  *state = &projInfo->pi_state;
    TupleTableSlot *slot = state->resultslot;
    bool        isnull;

    /*
     * Clear any former contents of the result slot.  This makes it safe for
     * us to use the slot's Datum/isnull arrays as workspace.
     * 清除以前的結(jié)果slot內(nèi)容。
     * 這使得我們可以安全地使用slot的Datum/isnull數(shù)組作為工作區(qū)。
     */
    ExecClearTuple(slot);

    /* Run the expression, discarding scalar result from the last column. */
    //運(yùn)行表達(dá)式,從最后一列丟棄scalar結(jié)果。
    (void) ExecEvalExprSwitchContext(state, econtext, &isnull);

    /*
     * Successfully formed a result row.  Mark the result slot as containing a
     * valid virtual tuple (inlined version of ExecStoreVirtualTuple()).
     * 成功形成了一個(gè)結(jié)果行。
     * 將結(jié)果slot標(biāo)記為包含一個(gè)有效的虛擬元組(ExecStoreVirtualTuple()的內(nèi)聯(lián)版本)。
     */
    slot->tts_flags &= ~TTS_FLAG_EMPTY;
    slot->tts_nvalid = slot->tts_tupleDescriptor->natts;

    return slot;
}
#endif

/*
 * ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
 * ExecPrepareQual).  Returns true if qual is satisfied, else false.
 * 解析用ExecInitQual準(zhǔn)備的條件qual(可能通過(guò)ExecPrepareQual)。
 * 如果滿足條件qual,返回true,否則為false。
 * 
 * Note: ExecQual used to have a third argument "resultForNull".  The
 * behavior of this function now corresponds to resultForNull == false.
 * If you want the resultForNull == true behavior, see ExecCheck.
 * 注意:ExecQual曾經(jīng)有第三個(gè)參數(shù)“resultForNull”。
 * 這個(gè)函數(shù)的行為現(xiàn)在對(duì)應(yīng)于resultForNull == false。
 * 如果希望resultForNull == true行為,請(qǐng)參閱ExecCheck。
 */
#ifndef FRONTEND
static inline bool
ExecQual(ExprState *state, ExprContext *econtext)
{
    Datum       ret;
    bool        isnull;

    /* short-circuit (here and in ExecInitQual) for empty restriction list */
    //如state為NULL,直接返回
    if (state == NULL)
        return true;

    /* verify that expression was compiled using ExecInitQual */
    //使用函數(shù)ExecInitQual驗(yàn)證表達(dá)式是否可以編譯
    Assert(state->flags & EEO_FLAG_IS_QUAL);

    ret = ExecEvalExprSwitchContext(state, econtext, &isnull);

    /* EEOP_QUAL不應(yīng)返回NULL;EEOP_QUAL should never return NULL */
    Assert(!isnull);

    return DatumGetBool(ret);
}
#endif



/* --------------------------------
 *      ExecClearTuple
 *
 *      This function is used to clear out a slot in the tuple table.
 *      該函數(shù)清空tuple table中的slot
 *      NB: only the tuple is cleared, not the tuple descriptor (if any).
 *      注意:只有tuple被清除,而不是tuple描述符
 * --------------------------------
 */
TupleTableSlot *                /* 返回驗(yàn)證通過(guò)的slot;return: slot passed */
ExecClearTuple(TupleTableSlot *slot)    /* 存儲(chǔ)tuple的slot;slot in which to store tuple */
{
    /*
     * sanity checks
     * 安全檢查
     */
    Assert(slot != NULL);

    /*
     * Free the old physical tuple if necessary.
     * 如需要,釋放原有的物理元組
     */
    if (TTS_SHOULDFREE(slot))
    {
        heap_freetuple(slot->tts_tuple);//釋放元組
        slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
    }
    if (TTS_SHOULDFREEMIN(slot))
    {
        heap_free_minimal_tuple(slot->tts_mintuple);
        slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
    }

    slot->tts_tuple = NULL;//設(shè)置NULL值
    slot->tts_mintuple = NULL;

    /*
     * Drop the pin on the referenced buffer, if there is one.
     * 如果有的話,將pin放在已引用的緩沖區(qū)上。
     */
    if (BufferIsValid(slot->tts_buffer))
        ReleaseBuffer(slot->tts_buffer);//釋放緩沖區(qū)

    slot->tts_buffer = InvalidBuffer;

    /*
     * Mark it empty.
     * 標(biāo)記為空
     */
    slot->tts_flags |= TTS_FLAG_EMPTY;
    slot->tts_nvalid = 0;

    return slot;
}

三、跟蹤分析

測(cè)試腳本如下

testdb=# explain select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je 
testdb-# from t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je 
testdb(#                         from t_grxx gr inner join t_jfxx jf 
testdb(#                                        on gr.dwbh = dw.dwbh 
testdb(#                                           and gr.grbh = jf.grbh) grjf
testdb-# order by dw.dwbh;
                                        QUERY PLAN                                        
------------------------------------------------------------------------------------------
 Sort  (cost=20070.93..20320.93 rows=100000 width=47)
   Sort Key: dw.dwbh
   ->  Hash Join  (cost=3754.00..8689.61 rows=100000 width=47)
         Hash Cond: ((gr.dwbh)::text = (dw.dwbh)::text)
         ->  Hash Join  (cost=3465.00..8138.00 rows=100000 width=31)
               Hash Cond: ((jf.grbh)::text = (gr.grbh)::text)
               ->  Seq Scan on t_jfxx jf  (cost=0.00..1637.00 rows=100000 width=20)
               ->  Hash  (cost=1726.00..1726.00 rows=100000 width=16)
                     ->  Seq Scan on t_grxx gr  (cost=0.00..1726.00 rows=100000 width=16)
         ->  Hash  (cost=164.00..164.00 rows=10000 width=20)
               ->  Seq Scan on t_dwxx dw  (cost=0.00..164.00 rows=10000 width=20)
(11 rows)

啟動(dòng)gdb,設(shè)置斷點(diǎn),進(jìn)入ExecutePlan

(gdb) b ExecutePlan
Breakpoint 1 at 0x6db79d: file execMain.c, line 1694.
(gdb) c
Continuing.

Breakpoint 1, ExecutePlan (estate=0x14daf48, planstate=0x14db160, use_parallel_mode=false, operation=CMD_SELECT, 
    sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x14d9ed0, execute_once=true) at execMain.c:1694
warning: Source file is more recent than executable.
1694        current_tuple_count = 0;

查看輸入?yún)?shù)
planstate->type:T_SortState->排序Plan
planstate->ExecProcNode:ExecProcNodeFirst,封裝器
planstate->ExecProcNodeReal:ExecSort,實(shí)際的函數(shù)
use_parallel_mode:false,非并行模式
operation:CMD_SELECT,查詢操作
sendTuples:T,需要發(fā)送元組給客戶端
numberTuples:0,所有元組
direction:ForwardScanDirection
dest:printtup(console客戶端)
execute_once:T,只執(zhí)行一次

(gdb) p *estate
$1 = {type = T_EState, es_direction = ForwardScanDirection, es_snapshot = 0x1493e10, es_crosscheck_snapshot = 0x0, 
  es_range_table = 0x14d7c00, es_plannedstmt = 0x14d9d58, 
  es_sourceText = 0x13eeeb8 "select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je \nfrom t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je \n", ' ' <repeats 24 times>, "from t_grxx gr inner join t_jfxx jf \n", ' ' <repeats 34 times>..., 
  es_junkFilter = 0x0, es_output_cid = 0, es_result_relations = 0x0, es_num_result_relations = 0, 
  es_result_relation_info = 0x0, es_root_result_relations = 0x0, es_num_root_result_relations = 0, 
  es_tuple_routing_result_relations = 0x0, es_trig_target_relations = 0x0, es_trig_tuple_slot = 0x0, 
  es_trig_oldtup_slot = 0x0, es_trig_newtup_slot = 0x0, es_param_list_info = 0x0, es_param_exec_vals = 0x0, 
  es_queryEnv = 0x0, es_query_cxt = 0x14dae30, es_tupleTable = 0x14dbaf8, es_rowMarks = 0x0, es_processed = 0, 
  es_lastoid = 0, es_top_eflags = 16, es_instrument = 0, es_finished = false, es_exprcontexts = 0x14db550, 
  es_subplanstates = 0x0, es_auxmodifytables = 0x0, es_per_tuple_exprcontext = 0x0, es_epqTuple = 0x0, 
  es_epqTupleSet = 0x0, es_epqScanDone = 0x0, es_use_parallel_mode = false, es_query_dsa = 0x0, es_jit_flags = 0, 
  es_jit = 0x0, es_jit_worker_instr = 0x0}
(gdb) p *planstate
$2 = {type = T_SortState, plan = 0x14d3f90, state = 0x14daf48, ExecProcNode = 0x6e41bb <ExecProcNodeFirst>, 
  ExecProcNodeReal = 0x716144 <ExecSort>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0, 
  qual = 0x0, lefttree = 0x14db278, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, 
  ps_ResultTupleSlot = 0x14ec470, ps_ExprContext = 0x0, ps_ProjInfo = 0x0, scandesc = 0x14e9fd0}
(gdb) p *dest
$4 = {receiveSlot = 0x48cc00 <printtup>, rStartup = 0x48c5c1 <printtup_startup>, rShutdown = 0x48d02e <printtup_shutdown>, 
  rDestroy = 0x48d0a7 <printtup_destroy>, mydest = DestRemote}

賦值,準(zhǔn)備執(zhí)行ExecProcNode(ExecSort)

(gdb) n
1699        estate->es_direction = direction;
(gdb) 
1705        if (!execute_once)
(gdb) 
1708        estate->es_use_parallel_mode = use_parallel_mode;
(gdb) 
1709        if (use_parallel_mode)
(gdb) 
1718            ResetPerTupleExprContext(estate);
(gdb) 
1723            slot = ExecProcNode(planstate);
(gdb)

執(zhí)行ExecProcNode(ExecSort),返回slot

(gdb) 
1729            if (TupIsNull(slot))
(gdb) p *slot
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false, 
  tts_tuple = 0x14ec4b0, tts_tupleDescriptor = 0x14ec058, tts_mcxt = 0x14dae30, tts_buffer = 0, tts_nvalid = 0, 
  tts_values = 0x14ec4d0, tts_isnull = 0x14ec508, tts_mintuple = 0x1a4b078, tts_minhdr = {t_len = 64, t_self = {ip_blkid = {
        bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}, tts_off = 0, 
  tts_fixedTupleDescriptor = true}

查看slot中的數(shù)據(jù)
注意:slot中的t_data不是實(shí)際的tuple data,而是緩沖區(qū)信息,在返回時(shí)根據(jù)這些信息從緩沖區(qū)獲取數(shù)據(jù)返回

(gdb) p *slot
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false, 
  tts_tuple = 0x14ec4b0, tts_tupleDescriptor = 0x14ec058, tts_mcxt = 0x14dae30, tts_buffer = 0, tts_nvalid = 0, 
  tts_values = 0x14ec4d0, tts_isnull = 0x14ec508, tts_mintuple = 0x1a4b078, tts_minhdr = {t_len = 64, t_self = {ip_blkid = {
        bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}, tts_off = 0, 
  tts_fixedTupleDescriptor = true}
(gdb) p *slot->tts_tuple
$6 = {t_len = 64, t_self = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}
(gdb) p *slot->tts_tuple->t_data
$7 = {t_choice = {t_heap = {t_xmin = 21967600, t_xmax = 0, t_field3 = {t_cid = 56, t_xvac = 56}}, t_datum = {
      datum_len_ = 21967600, datum_typmod = 0, datum_typeid = 56}}, t_ctid = {ip_blkid = {bi_hi = 0, bi_lo = 0}, 
    ip_posid = 32639}, t_infomask2 = 7, t_infomask = 2, t_hoff = 24 '\030', t_bits = 0x1a4b087 ""}

判斷是否需要過(guò)濾屬性(不需要)

(gdb) n
1748            if (estate->es_junkFilter != NULL)
(gdb) 
(gdb) p estate->es_junkFilter
$12 = (JunkFilter *) 0x0

修改計(jì)數(shù)器等信息

(gdb) 
1755            if (sendTuples)
(gdb) 
1762                if (!dest->receiveSlot(slot, dest))
(gdb) 
1771            if (operation == CMD_SELECT)
(gdb) 
1772                (estate->es_processed)++;
(gdb) p estate->es_processed
$9 = 0
(gdb) n
1779            current_tuple_count++;
(gdb) p current_tuple_count
$10 = 0
(gdb) n
1780            if (numberTuples && numberTuples == current_tuple_count)
(gdb) p numberTuples
$11 = 0
(gdb) n
1790        }

繼續(xù)循環(huán),直接滿足條件(全部掃描完畢)未知

(gdb) n
1718            ResetPerTupleExprContext(estate);
(gdb) 
1723            slot = ExecProcNode(planstate);
(gdb) 
1729            if (TupIsNull(slot))
...

ExecutePlan的主體邏輯已介紹完畢,下面簡(jiǎn)單跟蹤分析ExecSeqScan函數(shù)
設(shè)置斷點(diǎn),進(jìn)入ExecSeqScan

(gdb) del 1
(gdb) c
Continuing.

Breakpoint 2, ExecSeqScan (pstate=0x14e99a0) at nodeSeqscan.c:127
warning: Source file is more recent than executable.
127     SeqScanState *node = castNode(SeqScanState, pstate);

查看輸入?yún)?shù)
plan為SeqScan
ExecProcNode=ExecProcNodeReal,均為函數(shù)ExecSeqScan
targetlist為投影列信息

(gdb) p *pstate
$13 = {type = T_SeqScanState, plan = 0x14d5570, state = 0x14daf48, ExecProcNode = 0x714d59 <ExecSeqScan>, 
  ExecProcNodeReal = 0x714d59 <ExecSeqScan>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0, 
  qual = 0x0, lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, 
  ps_ResultTupleSlot = 0x14e9c38, ps_ExprContext = 0x14e9ab8, ps_ProjInfo = 0x0, scandesc = 0x7fa45b442ab8}
(gdb) p *pstate->plan
$14 = {type = T_SeqScan, startup_cost = 0, total_cost = 164, plan_rows = 10000, plan_width = 20, parallel_aware = false, 
  parallel_safe = true, plan_node_id = 7, targetlist = 0x14d5438, qual = 0x0, lefttree = 0x0, righttree = 0x0, 
  initPlan = 0x0, extParam = 0x0, allParam = 0x0}

進(jìn)入ExecScan函數(shù)
accessMtd方法為SeqNext
recheckMtd方法為SeqRecheck

(gdb) n
129     return ExecScan(&node->ss,
(gdb) step
ExecScan (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:132
warning: Source file is more recent than executable.
132     qual = node->ps.qual;

ExecScan->投影信息,為NULL

(gdb) p *projInfo
Cannot access memory at address 0x0

ExecScan->約束條件為NULL

(gdb) p *qual
Cannot access memory at address 0x0

ExecScan->如果既沒(méi)有要檢查的條件qual,也沒(méi)有要做的投影操作,那么就跳過(guò)所有的操作并返回raw scan元組

(gdb) n
142     if (!qual && !projInfo)
(gdb) 
144         ResetExprContext(econtext);
(gdb) n
145         return ExecScanFetch(node, accessMtd, recheckMtd);

ExecScan->進(jìn)入ExecScanFetch

(gdb) step
ExecScanFetch (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:39
39      EState     *estate = node->ps.state;

ExecScan->檢查中斷,判斷是否處于EvalPlanQual recheck狀態(tài)(為NULL,實(shí)際不是)

39      EState     *estate = node->ps.state;
(gdb) n
41      CHECK_FOR_INTERRUPTS();
(gdb) 
43      if (estate->es_epqTuple != NULL)
(gdb) p *estate->es_epqTuple
Cannot access memory at address 0x0

ExecScan->調(diào)用訪問(wèn)方法SeqNext,返回slot

(gdb) n
95      return (*accessMtd) (node);
(gdb) n
96  }

ExecScan->回到ExecScan&ExecSeqScan,結(jié)束調(diào)用

(gdb) n
ExecScan (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:219
219 }
(gdb) 
ExecSeqScan (pstate=0x14e99a0) at nodeSeqscan.c:132
132 }
(gdb)

到此,關(guān)于“PostgreSQL中ExecutePlan函數(shù)與ExecSeqScan函數(shù)的作用是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

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

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

AI