PostgreSQL Executor(3): 可優(yōu)化語句的執(zhí)行

可優(yōu)化語句經(jīng)過優(yōu)化器優(yōu)化后生成查詢計劃樹,并由Executor執(zhí)行。Executor對外有四個接口函數(shù):ExecutorStart、ExecutorRun、ExecutorFinish、ExecutorEnd。Executor的輸入是查詢描述符QueryDesc,輸出是結(jié)果數(shù)據(jù)或相關(guān)的執(zhí)行信息。

查詢描述符封裝了Executor執(zhí)行查詢需要的一切信息。QueryDesc定義在src/include/executor/execdesc.h中。

執(zhí)行查詢計劃樹,只需要構(gòu)造QueryDesc,并依次調(diào)用上面四個接口函數(shù)就能完成執(zhí)行過程。

Executor的接口函數(shù)全部在Portal的執(zhí)行過程中被調(diào)用。以PORTAL_ONE_SELECT的執(zhí)行策略為例:

  1. PortalStart中首先確定執(zhí)行策略。如果執(zhí)行策略是PORTAL_ONE_SELECT,則會創(chuàng)建QueryDesc,將查詢計劃樹賦給查詢描述符。然后執(zhí)行ExecutorStart完成Executor的初始化工作。

  2. PortalRun中調(diào)用PortalRunSelect,在其中執(zhí)行ExecutorRun,完成查詢計劃的執(zhí)行。

  3. PortalDrop中調(diào)用PortalCleanup,在其中執(zhí)行ExecutorFinishExecutorEnd以清理環(huán)境,最后釋放QueryDesc。

Executor的處理模式

Executor對查詢計劃樹的執(zhí)行過程,實際上是對計劃樹的每一個節(jié)點的處理。查詢樹的每一個節(jié)點都表示一種操作,節(jié)點的處理被設(shè)計成按需要驅(qū)動的模式。節(jié)點使用子節(jié)點輸出的數(shù)據(jù)作為輸入,按自身的操作邏輯處理之后向上層節(jié)點返回結(jié)果數(shù)據(jù)。實現(xiàn)上,從根節(jié)點開始處理,每個節(jié)點在處理過程中根據(jù)需要調(diào)用子節(jié)點的處理過程來獲取數(shù)據(jù)。通過遞歸的方式,實現(xiàn)整個計劃樹的遍歷執(zhí)行。

初始化和清理操作也是采用相同的模式,從根節(jié)點開始遞歸處理子節(jié)點。

計劃樹中的每一個節(jié)點都是一個操作符,完成一個具體的物理操作。在PostgreSQL中,操作符被定義為有0~2個輸入和1個輸出。這樣所有的操作符可以組織成一個二叉樹,下層節(jié)點的輸出是上層節(jié)點的輸入,直至根節(jié)點對外輸出結(jié)果數(shù)據(jù)。數(shù)據(jù)(元組)從葉子節(jié)點向上層節(jié)點流動,直至根節(jié)點完成處理。

在Executor中,通過ExecInitNodeExecProcNodeExecEndNode三個入口函數(shù)統(tǒng)一對節(jié)點進(jìn)行初始化、執(zhí)行和清理。每個節(jié)點都實現(xiàn)了對應(yīng)的初始化、執(zhí)行和清理函數(shù),并且通過三個入口函數(shù)從根節(jié)點開始遞歸執(zhí)行。

PostgreSQL采用一次一個元組的執(zhí)行模式,每個節(jié)點一次向上層節(jié)點返回一個元組。整個查詢計劃樹的節(jié)點就構(gòu)成了一個管道,查詢計劃樹的執(zhí)行過程可以看成拉動元組穿過管道的過程。

計劃節(jié)點的數(shù)據(jù)結(jié)構(gòu)

PostgreSQL采用面向?qū)ο蟮乃枷朐O(shè)計節(jié)點的數(shù)據(jù)結(jié)構(gòu),所有節(jié)點都繼承自Plan。Plan是所有節(jié)點的通用抽象類型。

Plan的定義在src/include/nodes/plannodes.h中,定義如下:

/* ----------------
 *        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)
 *
 * We never actually instantiate any Plan nodes; this is just the common
 * abstract superclass for all Plan-type nodes.
 * ----------------
 */
typedef struct Plan
{
    NodeTag     type;

    /*
     * estimated execution costs for plan (see costsize.c for more info)
     */
    Cost        startup_cost;   /* cost expended before fetching any tuples */
    Cost        total_cost;     /* total cost (assuming all tuples fetched) */

    /*
     * planner's estimate of result size of this plan step
     */
    double      plan_rows;      /* number of rows plan is expected to emit */
    int         plan_width;     /* average row width in bytes */

    /*
     * information needed for parallel query
     */
    bool        parallel_aware; /* engage parallel-aware logic? */
    bool        parallel_safe;  /* OK to use as part of parallel plan? */

    /*
     * 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
     *
     * 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;

Plan中定義了左右子樹(lefttree, righttree)、節(jié)點類型(type)、選擇表達(dá)式(qual)、投影列表(targetlist)等公共字段。

PostgreSQL將所有的計劃節(jié)點按功能分為四類:

  • 控制節(jié)點(control node)
  • 掃描節(jié)點(scan node)
  • 連接節(jié)點(join node)
  • 物化節(jié)點(materalization node)

其中,掃描和連接節(jié)點類型定義了公共父類ScanJoin。具體的節(jié)點繼承了公共父類并增加了與自身操作相關(guān)的擴(kuò)展字段。

節(jié)點通過左右子樹指針鏈接了子節(jié)點,根節(jié)點指針保存在PlannedStmt中。而PlannedStmt被存放在QueryDesc`中。

PostgreSQL為每一種計劃節(jié)點定義了一個狀態(tài)節(jié)點。與計劃節(jié)點類似,所有的狀態(tài)節(jié)點都繼承自PlanState,其中包含計劃節(jié)點指針、執(zhí)行器全局狀態(tài)結(jié)構(gòu)指針、投影運算信息、選擇運算條件,以及左右子狀態(tài)節(jié)點指針。狀態(tài)節(jié)點之間組成了與計劃樹類似的狀態(tài)樹。

在執(zhí)行器初始化時,ExecutorStart會根據(jù)查詢計劃樹構(gòu)造執(zhí)行器全局狀態(tài)(EState)以及計劃節(jié)點狀態(tài)樹。在查詢樹執(zhí)行過程中,執(zhí)行器將使用狀態(tài)節(jié)點記錄計劃節(jié)點的執(zhí)行狀態(tài)和數(shù)據(jù),并通過全局狀態(tài)在節(jié)點間傳遞元組。執(zhí)行器的清理函數(shù)ExecutorEnd將回收執(zhí)行器全局狀態(tài)和狀態(tài)節(jié)點。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容