可優(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í)行策略為例:
在
PortalStart中首先確定執(zhí)行策略。如果執(zhí)行策略是PORTAL_ONE_SELECT,則會創(chuàng)建QueryDesc,將查詢計劃樹賦給查詢描述符。然后執(zhí)行ExecutorStart完成Executor的初始化工作。在
PortalRun中調(diào)用PortalRunSelect,在其中執(zhí)行ExecutorRun,完成查詢計劃的執(zhí)行。在
PortalDrop中調(diào)用PortalCleanup,在其中執(zhí)行ExecutorFinish和ExecutorEnd以清理環(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中,通過ExecInitNode、ExecProcNode和ExecEndNode三個入口函數(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é)點類型定義了公共父類Scan和Join。具體的節(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é)點。