PortalRunSelect函數(shù)的實(shí)現(xiàn)邏輯是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小..."/>
溫馨提示×

溫馨提示×

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

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

PostgreSQL中PortalRun->PortalRunSelect函數(shù)的實(shí)現(xiàn)邏輯是什么

發(fā)布時(shí)間:2021-11-10 15:41:02 來源:億速云 閱讀:168 作者:iii 欄目:關(guān)系型數(shù)據(jù)庫

本篇內(nèi)容介紹了“PostgreSQL中PortalRun->PortalRunSelect函數(shù)的實(shí)現(xiàn)邏輯是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

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

Portal
對于Portals(客戶端請求),有幾種執(zhí)行策略,具體取決于要執(zhí)行什么查詢。
(注意:無論什么情況下,一個(gè)Portal只執(zhí)行一個(gè)source-SQL查詢,因此從用戶的角度來看只產(chǎn)生一個(gè)結(jié)果。

/*
 * We have several execution strategies for Portals, depending on what
 * query or queries are to be executed.  (Note: in all cases, a Portal
 * executes just a single source-SQL query, and thus produces just a
 * single result from the user's viewpoint.  However, the rule rewriter
 * may expand the single source query to zero or many actual queries.)
 * 對于Portals(客戶端請求),有幾種執(zhí)行策略,具體取決于要執(zhí)行什么查詢。
 * (注意:無論什么情況下,一個(gè)Portal只執(zhí)行一個(gè)source-SQL查詢,因此從用戶的角度來看只產(chǎn)生一個(gè)結(jié)果。
 * 但是,規(guī)則重寫器可以將單個(gè)源查詢擴(kuò)展為零或多個(gè)實(shí)際查詢。
 * 
 * PORTAL_ONE_SELECT: the portal contains one single SELECT query.  We run
 * the Executor incrementally as results are demanded.  This strategy also
 * supports holdable cursors (the Executor results can be dumped into a
 * tuplestore for access after transaction completion).
 * PORTAL_ONE_SELECT: 包含一個(gè)SELECT查詢。
 *                    按需要的結(jié)果重復(fù)(遞增)地運(yùn)行執(zhí)行器。
 *                    該策略還支持可持有游標(biāo)(執(zhí)行器結(jié)果可以在事務(wù)完成后轉(zhuǎn)儲(chǔ)到tuplestore中進(jìn)行訪問)。
 * 
 * PORTAL_ONE_RETURNING: the portal contains a single INSERT/UPDATE/DELETE
 * query with a RETURNING clause (plus possibly auxiliary queries added by
 * rule rewriting).  On first execution, we run the portal to completion
 * and dump the primary query's results into the portal tuplestore; the
 * results are then returned to the client as demanded.  (We can't support
 * suspension of the query partway through, because the AFTER TRIGGER code
 * can't cope, and also because we don't want to risk failing to execute
 * all the auxiliary queries.)
 * PORTAL_ONE_RETURNING: 包含一個(gè)帶有RETURNING子句的INSERT/UPDATE/DELETE查詢
                         (可能還包括由規(guī)則重寫添加的輔助查詢)。
 *                       在第一次執(zhí)行時(shí),運(yùn)行Portal來完成并將主查詢的結(jié)果轉(zhuǎn)儲(chǔ)到Portal的tuplestore中;
 *                       然后根據(jù)需要將結(jié)果返回給客戶端。
 *                       (我們不能支持半途中斷的查詢,因?yàn)锳FTER觸發(fā)器代碼無法處理,
 *                       也因?yàn)椴幌朊皥?zhí)行所有輔助查詢失敗的風(fēng)險(xiǎn))。
 * 
 * PORTAL_ONE_MOD_WITH: the portal contains one single SELECT query, but
 * it has data-modifying CTEs.  This is currently treated the same as the
 * PORTAL_ONE_RETURNING case because of the possibility of needing to fire
 * triggers.  It may act more like PORTAL_ONE_SELECT in future.
 * PORTAL_ONE_MOD_WITH: 只包含一個(gè)SELECT查詢,但它具有數(shù)據(jù)修改的CTEs。
 *                      這與PORTAL_ONE_RETURNING的情況相同,因?yàn)榭赡苄枰|發(fā)觸發(fā)器。將來它的行為可能更像PORTAL_ONE_SELECT。
 * 
 * PORTAL_UTIL_SELECT: the portal contains a utility statement that returns
 * a SELECT-like result (for example, EXPLAIN or SHOW).  On first execution,
 * we run the statement and dump its results into the portal tuplestore;
 * the results are then returned to the client as demanded.
 * PORTAL_UTIL_SELECT: 包含一個(gè)實(shí)用程序語句,該語句返回一個(gè)類似SELECT的結(jié)果(例如,EXPLAIN或SHOW)。
 *                     在第一次執(zhí)行時(shí),運(yùn)行語句并將其結(jié)果轉(zhuǎn)儲(chǔ)到portal tuplestore;然后根據(jù)需要將結(jié)果返回給客戶端。
 * 
 * PORTAL_MULTI_QUERY: all other cases.  Here, we do not support partial
 * execution: the portal's queries will be run to completion on first call.
 * PORTAL_MULTI_QUERY: 除上述情況外的其他情況。
 *                     在這里,不支持部分執(zhí)行:Portal的查詢語句將在第一次調(diào)用時(shí)運(yùn)行到完成。
 */
typedef enum PortalStrategy
{
    PORTAL_ONE_SELECT,
    PORTAL_ONE_RETURNING,
    PORTAL_ONE_MOD_WITH,
    PORTAL_UTIL_SELECT,
    PORTAL_MULTI_QUERY
} PortalStrategy;

/*
 * A portal is always in one of these states.  It is possible to transit
 * from ACTIVE back to READY if the query is not run to completion;
 * otherwise we never back up in status.
 * Portal總是處于這些狀態(tài)中的之一。
 * 如果查詢沒有運(yùn)行到完成,則可以從活動(dòng)狀態(tài)轉(zhuǎn)回準(zhǔn)備狀態(tài);否則永遠(yuǎn)不會(huì)后退。
 */
typedef enum PortalStatus
{
    PORTAL_NEW,                 /* 剛創(chuàng)建;freshly created */
    PORTAL_DEFINED,             /* PortalDefineQuery完成;PortalDefineQuery done */
    PORTAL_READY,               /* PortalStart完成;PortalStart complete, can run it */
    PORTAL_ACTIVE,              /* Portal正在運(yùn)行;portal is running (can't delete it) */
    PORTAL_DONE,                /* Portal已經(jīng)完成;portal is finished (don't re-run it) */
    PORTAL_FAILED               /* Portal出現(xiàn)錯(cuò)誤;portal got error (can't re-run it) */
} PortalStatus;

typedef struct PortalData *Portal;//結(jié)構(gòu)體指針

typedef struct PortalData
{
    /* Bookkeeping data */
    const char *name;           /* portal的名稱;portal's name */
    const char *prepStmtName;   /* 已完成準(zhǔn)備的源語句;source prepared statement (NULL if none) */
    MemoryContext portalContext;    /* 內(nèi)存上下文;subsidiary memory for portal */
    ResourceOwner resowner;     /* 資源的owner;resources owned by portal */
    void        (*cleanup) (Portal portal); /* cleanup鉤子函數(shù);cleanup hook */

    /*
     * State data for remembering which subtransaction(s) the portal was
     * created or used in.  If the portal is held over from a previous
     * transaction, both subxids are InvalidSubTransactionId.  Otherwise,
     * createSubid is the creating subxact and activeSubid is the last subxact
     * in which we ran the portal.
     * 狀態(tài)數(shù)據(jù),用于記住在哪個(gè)子事務(wù)中創(chuàng)建或使用Portal。
     * 如果Portal是從以前的事務(wù)中持有的,那么兩個(gè)subxids都應(yīng)該是InvalidSubTransactionId。
     * 否則,createSubid是正在創(chuàng)建的subxact,而activeSubid是運(yùn)行Portal的最后一個(gè)subxact。
     */
    SubTransactionId createSubid;   /* 正在創(chuàng)建的subxact;the creating subxact */
    SubTransactionId activeSubid;   /* 活動(dòng)的最后一個(gè)subxact;the last subxact with activity */

    /* The query or queries the portal will execute */
    //portal將會(huì)執(zhí)行的查詢
    const char *sourceText;     /* 查詢的源文本;text of query (as of 8.4, never NULL) */
    const char *commandTag;     /* 源查詢的命令tag;command tag for original query */
    List       *stmts;          /* PlannedStmt鏈表;list of PlannedStmts */
    CachedPlan *cplan;          /* 緩存的PlannedStmts;CachedPlan, if stmts are from one */

    ParamListInfo portalParams; /* 傳遞給查詢的參數(shù);params to pass to query */
    QueryEnvironment *queryEnv; /* 查詢的執(zhí)行環(huán)境;environment for query */

    /* Features/options */
    PortalStrategy strategy;    /* 場景;see above */
    int         cursorOptions;  /* DECLARE CURSOR選項(xiàng)位;DECLARE CURSOR option bits */
    bool        run_once;       /* 是否只執(zhí)行一次;portal will only be run once */

    /* Status data */
    PortalStatus status;        /* Portal的狀態(tài);see above */
    bool        portalPinned;   /* 是否不能被清除;a pinned portal can't be dropped */
    bool        autoHeld;       /* 是否自動(dòng)從pinned到held;was automatically converted from pinned to
                                 * held (see HoldPinnedPortals()) */

    /* If not NULL, Executor is active; call ExecutorEnd eventually: */
    //如不為NULL,執(zhí)行器處于活動(dòng)狀態(tài)
    QueryDesc  *queryDesc;      /* 執(zhí)行器需要使用的信息;info needed for executor invocation */

    /* If portal returns tuples, this is their tupdesc: */
    //如Portal需要返回元組,這是元組的描述
    TupleDesc   tupDesc;        /* 結(jié)果元組的描述;descriptor for result tuples */
    /* and these are the format codes to use for the columns: */
    //列信息的格式碼
    int16      *formats;        /* 每一列的格式碼;a format code for each column */

    /*
     * Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or
     * PORTAL_UTIL_SELECT query.  (A cursor held past the end of its
     * transaction no longer has any active executor state.)
     * 在這里,為持有的游標(biāo)或PORTAL_ONE_RETURNING或PORTAL_UTIL_SELECT存儲(chǔ)元組。
     * (在事務(wù)結(jié)束后持有的游標(biāo)不再具有任何活動(dòng)執(zhí)行器狀態(tài)。)
     */
    Tuplestorestate *holdStore; /* 存儲(chǔ)持有的游標(biāo)信息;store for holdable cursors */
    MemoryContext holdContext;  /* 持有holdStore的內(nèi)存上下文;memory containing holdStore */

    /*
     * Snapshot under which tuples in the holdStore were read.  We must keep a
     * reference to this snapshot if there is any possibility that the tuples
     * contain TOAST references, because releasing the snapshot could allow
     * recently-dead rows to be vacuumed away, along with any toast data
     * belonging to them.  In the case of a held cursor, we avoid needing to
     * keep such a snapshot by forcibly detoasting the data.
     * 讀取holdStore中元組的Snapshot。
     * 如果元組包含TOAST引用的可能性存在,那么必須保持對該快照的引用,
     * 因?yàn)獒尫趴煺湛赡軙?huì)使最近廢棄的行與屬于它們的TOAST數(shù)據(jù)一起被清除。
     * 對于持有的游標(biāo),通過強(qiáng)制解壓數(shù)據(jù)來避免需要保留這樣的快照。
     */
    Snapshot    holdSnapshot;   /* 已注冊的快照信息,如無則為NULL;registered snapshot, or NULL if none */

    /*
     * atStart, atEnd and portalPos indicate the current cursor position.
     * portalPos is zero before the first row, N after fetching N'th row of
     * query.  After we run off the end, portalPos = # of rows in query, and
     * atEnd is true.  Note that atStart implies portalPos == 0, but not the
     * reverse: we might have backed up only as far as the first row, not to
     * the start.  Also note that various code inspects atStart and atEnd, but
     * only the portal movement routines should touch portalPos.
     * atStart、atEnd和portalPos表示當(dāng)前光標(biāo)的位置。
     * portalPos在第一行之前為0,在獲取第N行查詢后為N。
     * 在運(yùn)行結(jié)束后,portalPos = #查詢中的行號,atEnd為T。
     * 注意,atStart表示portalPos == 0,但不是相反:我們可能只回到到第一行,而不是開始。
     * 還要注意,各種代碼在開始和結(jié)束時(shí)都要檢查,但是只有Portal移動(dòng)例程應(yīng)該訪問portalPos。
     */
    bool        atStart;//處于開始位置?
    bool        atEnd;//處于結(jié)束位置?
    uint64      portalPos;//實(shí)際行號

    /* Presentation data, primarily used by the pg_cursors system view */
    //用于表示的數(shù)據(jù),主要由pg_cursors系統(tǒng)視圖使用
    TimestampTz creation_time;  /* portal定義的時(shí)間;time at which this portal was defined */
    bool        visible;        /* 是否在pg_cursors中可見? include this portal in pg_cursors? */
}           PortalData;

/*
 * PortalIsValid
 *      True iff portal is valid.
 *      判斷Portal是否有效
 */
#define PortalIsValid(p) PointerIsValid(p)

QueryDesc
QueryDesc封裝了執(zhí)行器執(zhí)行查詢所需的所有內(nèi)容。

/* ----------------
 *      query descriptor:
 *
 *  a QueryDesc encapsulates everything that the executor
 *  needs to execute the query.
 *  QueryDesc封裝了執(zhí)行器執(zhí)行查詢所需的所有內(nèi)容。
 *
 *  For the convenience of SQL-language functions, we also support QueryDescs
 *  containing utility statements; these must not be passed to the executor
 *  however.
 *  為了使用SQL函數(shù),還需要支持包含實(shí)用語句的QueryDescs;
 *  但是,這些內(nèi)容不能傳遞給執(zhí)行程序。
 * ---------------------
 */
typedef struct QueryDesc
{
    /* These fields are provided by CreateQueryDesc */
    //以下變量由CreateQueryDesc函數(shù)設(shè)置
    CmdType     operation;      /* 操作類型,如CMD_SELECT等;CMD_SELECT, CMD_UPDATE, etc. */
    PlannedStmt *plannedstmt;   /* 已規(guī)劃的語句,規(guī)劃器的輸出;planner's output (could be utility, too) */
    const char *sourceText;     /* 源SQL文本;source text of the query */
    Snapshot    snapshot;       /* 查詢使用的快照;snapshot to use for query */
    Snapshot    crosscheck_snapshot;    /* RI 更新/刪除交叉檢查快照;crosscheck for RI update/delete */
    DestReceiver *dest;         /* 元組輸出的接收器;the destination for tuple output */
    ParamListInfo params;       /* 需傳入的參數(shù)值;param values being passed in */
    QueryEnvironment *queryEnv; /* 查詢環(huán)境變量;query environment passed in */
    int         instrument_options; /* InstrumentOption選項(xiàng);OR of InstrumentOption flags */

    /* These fields are set by ExecutorStart */
    //以下變量由ExecutorStart函數(shù)設(shè)置
    TupleDesc   tupDesc;        /* 結(jié)果元組tuples描述;descriptor for result tuples */
    EState     *estate;         /* 執(zhí)行器狀態(tài);executor's query-wide state */
    PlanState  *planstate;      /* per-plan-node狀態(tài)樹;tree of per-plan-node state */

    /* This field is set by ExecutorRun */
    //以下變量由ExecutorRun設(shè)置
    bool        already_executed;   /* 先前已執(zhí)行,則為T;true if previously executed */

    /* This is always set NULL by the core system, but plugins can change it */
    //內(nèi)核設(shè)置為NULL,可由插件修改
    struct Instrumentation *totaltime;  /* ExecutorRun函數(shù)所花費(fèi)的時(shí)間;total time spent in ExecutorRun */
} QueryDesc;

二、源碼解讀

PortalRun->PortalRunSelect函數(shù)執(zhí)行以PORTAL_ONE_SELECT模式運(yùn)行的SQL.

/*
 * PortalRunSelect
 *      Execute a portal's query in PORTAL_ONE_SELECT mode, and also
 *      when fetching from a completed holdStore in PORTAL_ONE_RETURNING,
 *      PORTAL_ONE_MOD_WITH, and PORTAL_UTIL_SELECT cases.
 *       執(zhí)行以PORTAL_ONE_SELECT模式運(yùn)行的SQL,同時(shí)處理PORTAL_ONE_RETURNING/
 *      PORTAL_ONE_MOD_WITH/PORTAL_UTIL_SELECT這幾種模式下完成holdStore后的數(shù)據(jù)提取
 *
 * This handles simple N-rows-forward-or-backward cases.  For more complex
 * nonsequential access to a portal, see PortalRunFetch.
 * 這將處理簡單的n行前向或后向情況。
 * 有關(guān)對門戶的更復(fù)雜的非順序訪問,請參閱PortalRunFetch。
 * 
 * count <= 0 is interpreted as a no-op: the destination gets started up
 * and shut down, but nothing else happens.  Also, count == FETCH_ALL is
 * interpreted as "all rows".  (cf FetchStmt.howMany)
 * count <= 0被解釋為一個(gè)no-op:目標(biāo)啟動(dòng)并關(guān)閉,但是沒有發(fā)生其他事情。
 * 另外,count == FETCH_ALL被解釋為“所有行”。(cf FetchStmt.howMany)
 * 
 * Caller must already have validated the Portal and done appropriate
 * setup (cf. PortalRun).
 * 調(diào)用者必須完成Portal的校驗(yàn)以及相關(guān)的配置.
 *
 * Returns number of rows processed (suitable for use in result tag)
 * 返回已處理的行數(shù).
 */
static uint64
PortalRunSelect(Portal portal,
                bool forward,
                long count,
                DestReceiver *dest)
{
    QueryDesc  *queryDesc;
    ScanDirection direction;
    uint64      nprocessed;

    /*
     * NB: queryDesc will be NULL if we are fetching from a held cursor or a
     * completed utility query; can't use it in that path.
     * 注意:從已持有的游標(biāo)或者已完成的工具類查詢中返回時(shí),queryDesc有可能是NULL.
     */
    queryDesc = portal->queryDesc;

    /* Caller messed up if we have neither a ready query nor held data. */
    //確保queryDescbuweiNULL或者持有提取的數(shù)據(jù)
    Assert(queryDesc || portal->holdStore);

    /*
     * Force the queryDesc destination to the right thing.  This supports
     * MOVE, for example, which will pass in dest = DestNone.  This is okay to
     * change as long as we do it on every fetch.  (The  must not
     * assume that dest never changes.)
     * 確保queryDesc目的地是正確的地方。
     * 例如,它支持MOVE,它將傳入dest = DestNone。
     * 只要在每次取回時(shí)都這樣做,這是可以改變的。(Executor不能假定dest永不改變。)
     */
    if (queryDesc)
        queryDesc->dest = dest;//設(shè)置dest

    /*
     * Determine which direction to go in, and check to see if we're already
     * at the end of the available tuples in that direction.  If so, set the
     * direction to NoMovement to avoid trying to fetch any tuples.  (This
     * check exists because not all plan node types are robust about being
     * called again if they've already returned NULL once.)  Then call the
     * executor (we must not skip this, because the destination needs to see a
     * setup and shutdown even if no tuples are available).  Finally, update
     * the portal position state depending on the number of tuples that were
     * retrieved.
     * 確定要進(jìn)入的方向,并檢查是否已經(jīng)在該方向的可用元組的末尾。
     * 如果是這樣,則將方向設(shè)置為NoMovement,以避免試圖再次獲取任何元組。
     * (之所以存在這種檢查,是因?yàn)椴皇撬械挠?jì)劃節(jié)點(diǎn)類型都能夠在已經(jīng)返回NULL時(shí)再次調(diào)用。)
     * 然后調(diào)用executor(我們不能跳過這一步,因?yàn)槟繕?biāo)需要看到設(shè)置和關(guān)閉,即使沒有元組可用)。
     * 最后,根據(jù)檢索到的元組數(shù)量更新Portal的數(shù)據(jù)位置狀態(tài)。
     */
    if (forward)//前向
    {
        if (portal->atEnd || count <= 0)
        {
            //已到末尾或者行計(jì)數(shù)小于等于0
            direction = NoMovementScanDirection;
            count = 0;          /* don't pass negative count to executor */
        }
        else
            direction = ForwardScanDirection;//前向掃描

        /* In the executor, zero count processes all rows */
        //在executor中,count=0意味著提取所有行
        if (count == FETCH_ALL)
            count = 0;

        if (portal->holdStore)
            //持有提取后的數(shù)據(jù)游標(biāo)
            nprocessed = RunFromStore(portal, direction, (uint64) count, dest);
        else
        {
            //沒有持有游標(biāo)(數(shù)據(jù))
            PushActiveSnapshot(queryDesc->snapshot);//快照入棧
            ExecutorRun(queryDesc, direction, (uint64) count,
                        portal->run_once);//開始執(zhí)行
            nprocessed = queryDesc->estate->es_processed;//結(jié)果行數(shù)
            PopActiveSnapshot();//快照出棧
        }

        if (!ScanDirectionIsNoMovement(direction))//掃描方向可移動(dòng)
        {
            if (nprocessed > 0)//掃描行數(shù)>0
                portal->atStart = false;    /* 可以向前移動(dòng)了;OK to go backward now */
            if (count == 0 || nprocessed < (uint64) count)
                //count為0或者行數(shù)小于傳入的計(jì)數(shù)器
                portal->atEnd = true;   /* 已完成掃描;we retrieved 'em all */
            portal->portalPos += nprocessed;//位置移動(dòng)(+處理行數(shù))
        }
    }
    else//非前向(后向)
    {
        if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL)//如游標(biāo)不可移動(dòng),報(bào)錯(cuò)
            ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                     errmsg("cursor can only scan forward"),
                     errhint("Declare it with SCROLL option to enable backward scan.")));

        if (portal->atStart || count <= 0)
        {
            //處于開始或者count小于等于0
            direction = NoMovementScanDirection;
            count = 0;          /* don't pass negative count to executor */
        }
        else
            //往后掃描
            direction = BackwardScanDirection;

        /* In the executor, zero count processes all rows */
        //參見forward=T的注釋
        if (count == FETCH_ALL)
            count = 0;

        if (portal->holdStore)
            nprocessed = RunFromStore(portal, direction, (uint64) count, dest);
        else
        {
            PushActiveSnapshot(queryDesc->snapshot);
            ExecutorRun(queryDesc, direction, (uint64) count,
                        portal->run_once);
            nprocessed = queryDesc->estate->es_processed;
            PopActiveSnapshot();
        }

        if (!ScanDirectionIsNoMovement(direction))
        {
            if (nprocessed > 0 && portal->atEnd)
            {
                portal->atEnd = false;  /* OK to go forward now */
                portal->portalPos++;    /* adjust for endpoint case */
            }
            if (count == 0 || nprocessed < (uint64) count)
            {
                portal->atStart = true; /* we retrieved 'em all */
                portal->portalPos = 0;
            }
            else
            {
                portal->portalPos -= nprocessed;
            }
        }
    }

    return nprocessed;
}


/*
 * RunFromStore
 *      Fetch tuples from the portal's tuple store.
 *      從Portal的tuple store中提取元組.
 *
 * Calling conventions are similar to ExecutorRun, except that we
 * do not depend on having a queryDesc or estate.  Therefore we return the
 * number of tuples processed as the result, not in estate->es_processed.
 * 該函數(shù)的調(diào)用約定類似于ExecutorRun,只是不依賴于是否擁有queryDesc或estate。
 * 因此,返回處理的元組的數(shù)量作為結(jié)果,而不是在estate->es_processed中返回。
 * 
 * One difference from ExecutorRun is that the destination receiver functions
 * are run in the caller's memory context (since we have no estate).  Watch
 * out for memory leaks.
 * 與ExecutorRun不同的是,目標(biāo)接收器函數(shù)在調(diào)用者的內(nèi)存上下文中運(yùn)行(因?yàn)闆]有estate)。
 * 需注意內(nèi)存泄漏!!!
 */
static uint64
RunFromStore(Portal portal, ScanDirection direction, uint64 count,
             DestReceiver *dest)
{
    uint64      current_tuple_count = 0;
    TupleTableSlot *slot;//元組表slot

    slot = MakeSingleTupleTableSlot(portal->tupDesc);

    dest->rStartup(dest, CMD_SELECT, portal->tupDesc);//目標(biāo)啟動(dòng)

    if (ScanDirectionIsNoMovement(direction))//無法移動(dòng)
    {
        /* do nothing except start/stop the destination */
        //不需要做任何事情
    }
    else
    {
        bool        forward = ScanDirectionIsForward(direction);//是否前向掃描

        for (;;)//循環(huán)
        {
            MemoryContext oldcontext;//內(nèi)存上下文
            bool        ok;

            oldcontext = MemoryContextSwitchTo(portal->holdContext);//切換至相應(yīng)的內(nèi)存上下文

            ok = tuplestore_gettupleslot(portal->holdStore, forward, false,
                                         slot);//獲取元組

            MemoryContextSwitchTo(oldcontext);//切換回原上下文

            if (!ok)
                break;//如出錯(cuò),則跳出循環(huán)

            /*
             * 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ā)送元組到目標(biāo)端,那么我們假設(shè)目標(biāo)端已經(jīng)關(guān)閉,不能發(fā)送更多元組。
             * 如果是這樣,結(jié)束循環(huán)。
             */
            if (!dest->receiveSlot(slot, dest))
                break;

            ExecClearTuple(slot);//執(zhí)行清理

            /*
             * check our tuple count.. if we've processed the proper number
             * then quit, else loop again and process more tuples. Zero count
             * means no limit.
             * 檢查元組計(jì)數(shù)…如果處理了正確的計(jì)數(shù),那么退出,
             * 否則再次循環(huán)并處理更多元組。零計(jì)數(shù)意味著沒有限制。
             */
            current_tuple_count++;
            if (count && count == current_tuple_count)
                break;
        }
    }

    dest->rShutdown(dest);//關(guān)閉目標(biāo)端

    ExecDropSingleTupleTableSlot(slot);//清除slot

    return current_tuple_count;//返回行數(shù)
}
 

/* ----------------------------------------------------------------
 *      ExecutorRun
 *      ExecutorRun函數(shù)
 *
 *      This is the main routine of the executor module. It accepts
 *      the query descriptor from the traffic cop and executes the
 *      query plan.
 *      這是executor模塊的主要實(shí)現(xiàn)例程。它接受traffic cop的查詢描述符并執(zhí)行查詢計(jì)劃。
 *
 *      ExecutorStart must have been called already.
 *      在此之前,已調(diào)用ExecutorStart函數(shù).
 *  
 *      If direction is NoMovementScanDirection then nothing is done
 *      except to start up/shut down the destination.  Otherwise,
 *      we retrieve up to 'count' tuples in the specified direction.
 *      如果方向是NoMovementScanDirection,那么除了啟動(dòng)/關(guān)閉目標(biāo)之外什么也不做。
 *      否則,在指定的方向上檢索指定數(shù)量“count”的元組。
 *
 *      Note: count = 0 is interpreted as no portal limit, i.e., run to
 *      completion.  Also note that the count limit is only applied to
 *      retrieved tuples, not for instance to those inserted/updated/deleted
 *      by a ModifyTable plan node.
 *      注意:count = 0被解釋為沒有限制,即,運(yùn)行到完成。
 *      還要注意,計(jì)數(shù)限制只適用于檢索到的元組,而不適用于由ModifyTable計(jì)劃節(jié)點(diǎn)插入/更新/刪除的元組。
 *
 *      There is no return value, but output tuples (if any) are sent to
 *      the destination receiver specified in the QueryDesc; and the number
 *      of tuples processed at the top level can be found in
 *      estate->es_processed.
 *      沒有返回值,但是輸出元組(如果有的話)被發(fā)送到QueryDesc中指定的目標(biāo)接收器;
 *      在頂層處理的元組數(shù)量可以在estate-> es_processing中找到。
 *
 *      We provide a function hook variable that lets loadable plugins
 *      get control when ExecutorRun is called.  Such a plugin would
 *      normally call standard_ExecutorRun().
 *      我們提供了一個(gè)鉤子函數(shù)變量,可以讓插件在調(diào)用ExecutorRun時(shí)獲得控制權(quán)。
 *      這樣的插件通常會(huì)調(diào)用standard_ExecutorRun()函數(shù)。
 *
 * ----------------------------------------------------------------
 */
void
ExecutorRun(QueryDesc *queryDesc,
            ScanDirection direction, uint64 count,
            bool execute_once)
{
    if (ExecutorRun_hook)
        (*ExecutorRun_hook) (queryDesc, direction, count, execute_once);//鉤子函數(shù)
    else
        standard_ExecutorRun(queryDesc, direction, count, execute_once);//標(biāo)準(zhǔn)函數(shù)
}

void
standard_ExecutorRun(QueryDesc *queryDesc,
                     ScanDirection direction, uint64 count, bool execute_once)
{
    EState     *estate;//全局執(zhí)行狀態(tài)
    CmdType     operation;//命令類型
    DestReceiver *dest;//接收器
    bool        sendTuples;//是否需要傳輸元組
    MemoryContext oldcontext;//內(nèi)存上下文

    /* sanity checks */
    Assert(queryDesc != NULL);//校驗(yàn)queryDesc不能為NULL

    estate = queryDesc->estate;//獲取執(zhí)行狀態(tài)

    Assert(estate != NULL);//執(zhí)行狀態(tài)不能為NULL
    Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));//eflags標(biāo)記不能為EXEC_FLAG_EXPLAIN_ONLY

    /*
     * Switch into per-query memory context
     * 切換內(nèi)存上下文
     */
    oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);

    /* Allow instrumentation of Executor overall runtime */
    //允許全程instrumentation
    if (queryDesc->totaltime)
        InstrStartNode(queryDesc->totaltime);

    /*
     * extract information from the query descriptor and the query feature.
     * 從查詢描述符和查詢特性中提取信息。
     */
    operation = queryDesc->operation;
    dest = queryDesc->dest;

    /*
     * startup tuple receiver, if we will be emitting tuples
     * 如需發(fā)送元組,則啟動(dòng)元組接收器
     */
    estate->es_processed = 0;
    estate->es_lastoid = InvalidOid;

    sendTuples = (operation == CMD_SELECT ||
                  queryDesc->plannedstmt->hasReturning);

    if (sendTuples)//如需發(fā)送元組
        dest->rStartup(dest, operation, queryDesc->tupDesc);

    /*
     * run plan
     * 執(zhí)行Plan
     */
    if (!ScanDirectionIsNoMovement(direction))//如非ScanDirectionIsNoMovement
    {
        if (execute_once && queryDesc->already_executed)//校驗(yàn)
            elog(ERROR, "can't re-execute query flagged for single execution");
        queryDesc->already_executed = true;//修改標(biāo)記

        ExecutePlan(estate,
                    queryDesc->planstate,
                    queryDesc->plannedstmt->parallelModeNeeded,
                    operation,
                    sendTuples,
                    count,
                    direction,
                    dest,
                    execute_once);//執(zhí)行Plan
    }

    /*
     * shutdown tuple receiver, if we started it
     * 如啟動(dòng)了元組接收器,則關(guān)閉它
     */
    if (sendTuples)
        dest->rShutdown(dest);

    if (queryDesc->totaltime)//收集時(shí)間
        InstrStopNode(queryDesc->totaltime, estate->es_processed);

    MemoryContextSwitchTo(oldcontext);//切換內(nèi)存上下文
}

三、跟蹤分析

測試腳本如下

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)入PortalRunSelect

(gdb) b PortalRunSelect
Breakpoint 1 at 0x8cc0e8: file pquery.c, line 888.
(gdb) c
Continuing.

Breakpoint 1, PortalRunSelect (portal=0x1af2468, forward=true, count=9223372036854775807, dest=0x1b74668) at pquery.c:888
warning: Source file is more recent than executable.
888     queryDesc = portal->queryDesc;
(gdb)

查看輸入?yún)?shù)portal&dest,forward為T表示前向掃描
portal:未命名的Portal,holdStore為NULL,atStart = true, atEnd = false, portalPos = 0
dest:接收器slot為printtup

(gdb) p *portal
$1 = {name = 0x1af5e90 "", prepStmtName = 0x0, portalContext = 0x1b795d0, resowner = 0x1abde80, 
  cleanup = 0x6711b6 <PortalCleanup>, createSubid = 1, activeSubid = 1, 
  sourceText = 0x1a8ceb8 "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>..., 
  commandTag = 0xc5eed5 "SELECT", stmts = 0x1b74630, cplan = 0x0, portalParams = 0x0, queryEnv = 0x0, 
  strategy = PORTAL_ONE_SELECT, cursorOptions = 4, run_once = true, status = PORTAL_ACTIVE, portalPinned = false, 
  autoHeld = false, queryDesc = 0x1b796e8, tupDesc = 0x1b867d8, formats = 0x1b79780, holdStore = 0x0, holdContext = 0x0, 
  holdSnapshot = 0x0, atStart = true, atEnd = false, portalPos = 0, creation_time = 595566906253867, visible = false}
(gdb) p *dest
$2 = {receiveSlot = 0x48cc00 <printtup>, rStartup = 0x48c5c1 <printtup_startup>, rShutdown = 0x48d02e <printtup_shutdown>, 
  rDestroy = 0x48d0a7 <printtup_destroy>, mydest = DestRemote}

校驗(yàn)并設(shè)置dest

(gdb) n
891     Assert(queryDesc || portal->holdStore);
(gdb) 
899     if (queryDesc)
(gdb) 
900         queryDesc->dest = dest;

前向掃描

(gdb) n
913     if (forward)
(gdb) 
915         if (portal->atEnd || count <= 0)

進(jìn)入ExecutorRun

...
(gdb) 
932             ExecutorRun(queryDesc, direction, (uint64) count,
(gdb) step
ExecutorRun (queryDesc=0x1b796e8, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:304
warning: Source file is more recent than executable.
304     if (ExecutorRun_hook)

進(jìn)入standard_ExecutorRun

(gdb) n
307         standard_ExecutorRun(queryDesc, direction, count, execute_once);
(gdb) step
standard_ExecutorRun (queryDesc=0x1b796e8, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:321
321     Assert(queryDesc != NULL);

standard_ExecutorRun->校驗(yàn)并切換上下文

321     Assert(queryDesc != NULL);
(gdb) n
323     estate = queryDesc->estate;
(gdb) 
325     Assert(estate != NULL);
(gdb) 
326     Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));
(gdb) 
331     oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
(gdb)

standard_ExecutorRun->變量賦值,判斷是否需要傳輸元組

(gdb) 
334     if (queryDesc->totaltime)
(gdb) n
340     operation = queryDesc->operation;
(gdb) 
341     dest = queryDesc->dest;
(gdb) p operation
$3 = CMD_SELECT
(gdb) n
346     estate->es_processed = 0;
(gdb) 
347     estate->es_lastoid = InvalidOid;
(gdb) 
349     sendTuples = (operation == CMD_SELECT ||
(gdb) 
352     if (sendTuples)
(gdb) 
353         dest->rStartup(dest, operation, queryDesc->tupDesc);
(gdb) p sendTuples
$4 = true
(gdb)

standard_ExecutorRun->執(zhí)行計(jì)劃(ExecutePlan函數(shù)下節(jié)介紹)

(gdb) n
358     if (!ScanDirectionIsNoMovement(direction))
(gdb) 
360         if (execute_once && queryDesc->already_executed)
(gdb) 
362         queryDesc->already_executed = true;
(gdb) 
364         ExecutePlan(estate,
(gdb)

standard_ExecutorRun->關(guān)閉資源并切換上下文

(gdb) 
378     if (sendTuples)
(gdb) n
379         dest->rShutdown(dest);
(gdb) 
381     if (queryDesc->totaltime)
(gdb) 
384     MemoryContextSwitchTo(oldcontext);
(gdb) 
385 }
(gdb)

standard_ExecutorRun->回到PortalRunSelect

(gdb) n
ExecutorRun (queryDesc=0x1b796e8, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:308
308 }
(gdb) 
PortalRunSelect (portal=0x1af2468, forward=true, count=0, dest=0x1b74668) at pquery.c:934
934             nprocessed = queryDesc->estate->es_processed;

快照出棧,修改狀態(tài)atStart/atEnd等

(gdb) n
935             PopActiveSnapshot();
(gdb) 
938         if (!ScanDirectionIsNoMovement(direction))
(gdb) 
940             if (nprocessed > 0)
(gdb) p nprocessed
$6 = 99991
(gdb) n
941                 portal->atStart = false;    /* OK to go backward now */
(gdb) 
942             if (count == 0 || nprocessed < (uint64) count)
(gdb)

完成調(diào)用

(gdb) n
943                 portal->atEnd = true;   /* we retrieved 'em all */
(gdb) p count
$7 = 0
(gdb) n
944             portal->portalPos += nprocessed;
(gdb) 
997     return nprocessed;
(gdb) 
998 }
(gdb) n
PortalRun (portal=0x1af2468, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x1b74668, altdest=0x1b74668, 
    completionTag=0x7ffc5ff58740 "") at pquery.c:780
780                 if (completionTag && portal->commandTag)
(gdb) p nprocessed
$8 = 99991

“PostgreSQL中PortalRun->PortalRunSelect函數(shù)的實(shí)現(xiàn)邏輯是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

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

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

AI