跟我一起讀postgresql源碼(十)——Executor(查詢執行模組之——Scan節點(下))

來源:互聯網
上載者:User

標籤:gre   method   each   查詢最佳化   semi   編譯器   present   表示   void   

接前文跟我一起讀postgresql源碼(九)——Executor(查詢執行模組之——Scan節點(上)) ,本篇把剩下的七個Scan節點結束掉。

    T_SubqueryScanState,    T_FunctionScanState,    T_ValuesScanState,    T_CteScanState,    T_WorkTableScanState,    T_ForeignScanState,    T_CustomScanState,
8.SubqueryScan 節點

SubqueryScan節點的作用是以另一個査詢計劃樹(子計劃)為掃描對象進行元組的掃描,其掃描過程最終被轉換為子計劃的執行。

Postgres子查詢主要包含如下幾個關鍵字: EXISTS, IN, NOT IN, ANY/SOME, ALL,詳細介紹可以看看:http://www.postgres.cn/docs/9.5/functions-subquery.html

舉例子:

postgres=# explain select id  from test_new where exists (select id from test_dm);                               QUERY PLAN------------------------------------------------------------------------- Result  (cost=0.02..35.52 rows=2550 width=4)   One-Time Filter: $0   InitPlan 1 (returns $0)     ->  Seq Scan on test_dm  (cost=0.00..22346.00 rows=1000000 width=0)   ->  Seq Scan on test_new  (cost=0.00..35.50 rows=2550 width=4)(5 行)

下面這個查詢雖然也是子查詢,但是在查詢編譯階段被最佳化了(提升子串連,主要是把ANY和EXIST子句轉換為半串連)

postgres=# explain select id  from test_new where exists (select id from test_dm where id = test_new.id);                                 QUERY PLAN----------------------------------------------------------------------------- Hash Semi Join  (cost=38753.00..42736.38 rows=1275 width=4)   Hash Cond: (test_new.id = test_dm.id)   ->  Seq Scan on test_new  (cost=0.00..35.50 rows=2550 width=4)   ->  Hash  (cost=22346.00..22346.00 rows=1000000 width=4)         ->  Seq Scan on test_dm  (cost=0.00..22346.00 rows=1000000 width=4)(5 行)

有關內容,這裡有一篇講得很好:PostgreSQL查詢最佳化之子查詢最佳化

SubqueryScan節點在Scan節點之上擴充定義了子計劃的根節點指標(subplan欄位),而subrtable欄位是査詢編譯器使用的結構,執行器運行時其值為空白。

typedef struct SubqueryScan{    Scan        scan;    Plan       *subplan;} SubqueryScan;

顯然,SubqueryScan節點的初始化過程(ExecInitSubqueryScan函數)會使用ExecInitNode處理SubqueryScan的subplan欄位指向的子計劃樹,並將子計劃的PlanStale樹根節點指標賦值給SubqueryScanState 的subplan欄位。

typedef struct SubqueryScanState{    ScanState   ss;             /* its first field is NodeTag */    PlanState  *subplan;} SubqueryScanState;

我認為SubqueryScan節點其實就是一個殼子,為什麼這麼說呢?因為SubqueryScan節點的執行(ExecSubqueryScan 函數)通過將SubqueryNext 傳遞給 ExecScan函數處理來實現的。SubqueryNext實際則是調用ExecProcNode處理subplan來獲得元組。也就是說,這裡SubqueryScan是運行了一個獨立的查詢計劃,然後擷取它的結果,而不是自己去掃描表。因此recheck工作就在獨立的查詢計劃裡做過了,SubqueryScan節點不必再做。

所以我們可以看到:

static boolSubqueryRecheck(SubqueryScanState *node, TupleTableSlot *slot){    /* nothing to check */    return true;}

上面說了在執行時調用了ExecProcNode處理subplan,那麼在清理過程中,很顯然需要額外調用ExecEndNode來清理子計劃。

9.FuncitonScan 節點

二話不說先上例子:

postgres=# CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text)postgres-#     AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$postgres-#     LANGUAGE SQL;CREATE FUNCTIONpostgres=# explain SELECT * FROM dup(42);                         QUERY PLAN------------------------------------------------------------- Function Scan on dup  (cost=0.25..10.25 rows=1000 width=36)(1 行)

在PostgreSQL中,有一些函數可以返回元組的集合,為了能從這些函數的傳回值中擷取元組,PostgreSQL定義了 FunctionScan節點,其掃描對象為返回元組集的函數。FunctionScan節點在Scan的基礎上擴充定義了:

functions列表欄位,裡面存放了FuncitonScan涉及的函數;

以及funcordinality欄位(是否給返回結果加上序號列)。

When a function in the FROM clause is suffixed by WITH ORDINALITY, a bigint column is appended to the output which starts from 1 and increments by 1 for each row of the function's output. This is most useful in the case of set returning functions such as unnest()

詳細看這裡:http://www.postgres.cn/docs/9.5/functions-srf.html

typedef struct FunctionScan{    Scan        scan;    List       *functions;      /* list of RangeTblFunction nodes */    bool        funcordinality; /* WITH ORDINALITY */} FunctionScan;

FunctionScan 節點的初始化過程(ExecInitFunctionScan 函數)會初始化 FunctionScanState 結構,然後根據FunctionScan的欄位functions,對每個函數構造運行時的狀態節點FunctionScanPerFuncState,如下:

typedef struct FunctionScanPerFuncState{    ExprState  *funcexpr;       /* state of the expression being evaluated */    TupleDesc   tupdesc;        /* desc of the function result type */    int         colcount;       /* expected number of result columns */    Tuplestorestate *tstore;    /* holds the function result set */    int64       rowcount;       /* # of rows in result set, -1 if not known */    TupleTableSlot *func_slot;  /* function result slot (or NULL) */} FunctionScanPerFuncState;

這雷根據FunctionScan中的functions欄位對每一個函數構造用於運算式計算的結構(儲存在funcexpr中)和,還要建構函式返回元組的描述符儲存在tupdesc中,此時用於儲存函數結果集的tuplestoreslate欄位為NULL。
上面這些做完以後,就可以根據所涉及的所有函數的FunctionScanPerFuncState結構來構造傳回值的TupleDesc(即最後的傳回值一定是這幾個函數傳回值的組合):
例如:

postgres=# SELECT * FROM dup(42) WITH ORDINALITY AS t(ls,n,xxx),increment(42); ls |     n      | xxx | increment----+------------+-----+----------- 42 | 42 is text |   1 |        43(1 行)
typedef struct FunctionScanState{    ScanState   ss;             /* its first field is NodeTag */    int         eflags;         //node's capability flags    bool        ordinality;     //is this scan WITH ORDINALITY?    bool        simple;         //true if we have 1 function and no ordinality    int64       ordinal;        //current ordinal column value    int         nfuncs;         //number of functions being executed    /* per-function execution states (private in nodeFunctionscan.c) */    struct FunctionScanPerFuncState *funcstates;        /* array of length nfuncs */    MemoryContext argcontext;   //memory context to evaluate function arguments in} FunctionScanState;

在 FunctionScan 節點的執行過程(ExecFunctionScan 函數)中,將 FunctionNext 傳遞給 ExecScan函數,FunctionNext函數首先判斷tuplestorestate是否為空白(首次執行時為空白),如果為空白則執行函數ExecMakeTableFunctionResult產生所有結果集並儲存在tuplestorestate中,此後每次執行節點將調用tuplestore_gettupleslot擷取結果集中的一個元組。

最後,FunctionScan節點清理過程需要淸理tuplestorestate結構。

10.ValuesScan 節點

VALUES計算由值運算式指定的一個行值或者一組行值。更常見的是把它用來產生一個大型命令內的"常量表", 但是它也可以被獨自使用。

當多於一行被指定時,所有行都必須具有相同數量的元素。結果表的列資料類型 由出現在該列的運算式的顯式或者推導類型組合決定,決定的規則與UNION相同。

在大型的命令中,在文法上允許VALUES出現在 SELECT出現的任何地方。因為文法把它當做一個 SELECT,可以為一個VALUES 命令使用ORDER BY、 LIMIT(或者等效的FETCH FIRST) 以及OFFSET子句。

我們舉例吧,一個純粹的VALUES命令:

VALUES (1, 'one'), (2, 'two'), (3, 'three');

將返回一個具有兩列、三行的表。

postgres=# VALUES (1, 'one'), (2, 'two'), (3, 'three'); column1 | column2---------+---------       1 | one       2 | two       3 | three(3 行)postgres=# EXPLAIN VALUES (1, 'one'), (2, 'two'), (3, 'three');                          QUERY PLAN-------------------------------------------------------------- Values Scan on "*VALUES*"  (cost=0.00..0.04 rows=3 width=36)(1 行)

更常用地,VALUES可以被用在一個大型 SQL 命令中。 在INSERT中最常用:

postgres=# insert into test values (1,'xxxx');INSERT 0 1postgres=# explain insert into test_new values (1);                      QUERY PLAN------------------------------------------------------ Insert on test_new  (cost=0.00..0.01 rows=1 width=0)   ->  Result  (cost=0.00..0.01 rows=1 width=0)(2 行)

具體的可以看這個:http://www.postgres.cn/docs/9.5/sql-values.html
這樣我們就對VALUES子句不陌生了,下面繼續說。

ValuesScan節點是用來對VALUES子句給出的元組集合進行掃描(INSERT語句中的VALUES子句走的是RESULT節點)。如下所示,ValuesScan節點中的values_lists儲存了VALUES子句中的運算式鏈表。

typedef struct ValuesScan{    Scan        scan;    List       *values_lists;   /* list of expression lists */} ValuesScan;

ValuesScan節點的初始化過程(ExeclnitValuesScan函數)處理values_lists中的運算式產生Values運算式,並儲存在ValuesScanState的exprlists數組中,array_len記錄數組長度,cuxr_idx和
markedJdx用於儲存數組中的位移量。同時還會分配記憶體上下文rowconext用於運算式計箅(ss.ps.ps_ExprContext本來就是用來做運算式計算的,但是為了防止對於一個過長的VALUES子句發生的記憶體泄露,使用rowconext對VALUES每一行做統一處理,在每一行處理完成後就使用rowconext釋放該段記憶體。)。

typedef struct ValuesScanState{    ScanState   ss;             /* its first field is NodeTag */    ExprContext *rowcontext;    //per-expression-list context    List      **exprlists;      //array of expression lists being evaluated    int         array_len;      //size of array    int         curr_idx;       //current array index (0-based)} ValuesScanState;

ValuesScan 節點執行過程(ExecValuesScan 函數)調用 ExecScan 實現,ExecScan 通過 ValuesNext擷取掃描元組,ValuesNext則通過curr_idx從exprlists中擷取需要處理的運算式,並計算出結果元組返回。

由於額外地申請了rowconext上下文,因此在ValuesScan節點清理過程(ExecEndValuesScan函數)中需要釋放記憶體上下文rowcontext。

11.CteScan 節點

WITH提供了一種方式來書寫在一個大型查詢中使用的輔助語句。這些語句通常被稱為公用表運算式或CTE,它們可以被看成是定義只在一個查詢中存在的暫存資料表。在WITH子句中的每一個輔助語句可以是一個SELECT、INSERT、UPDATE或DELETE,並且WITH子句本身也可以被附加到一個主語句,主語句也可以是SELECT、INSERT、UPDATE或DELETE
具體可以參考這個:http://www.postgres.cn/docs/9.5/queries-with.html

如果對CTE有所瞭解,就會知道,CTE一般不會單獨存在,而是依附於一個主查詢,換言之CTE是作為一個副查詢出現的。所以在主查詢中就將副查詢作為一個子計劃Subplan處理。CTE的執行狀態樹存放到執行器全域狀態Estate的es_subplanstates鏈表中。

typedef struct EState{    NodeTag     type;    ...    /* Parameter info: */    ParamListInfo es_param_list_info;   /* values of external params */    ParamExecData *es_param_exec_vals;  /* values of internal params */    ...    List       *es_subplanstates;       /* List of PlanState for SubPlans */    ...} EState;

並在CteScan中的ctePlanld儲存其子計劃在該鏈表中的位移量,對應於同一個子計劃的CteScan的ctePlanld相同。PostgreSQL在實現時,還為每個CTE在一個全域參數鏈表中分配了一個空間,其位移量儲存在cteParam中,對應同一個CTE的CteScan對應的位移量也相同。CteScan節點相關資料結構如下所示。

typedef struct CteScan{    Scan        scan;    int         ctePlanId;      /* ID of init SubPlan for CTE */    int         cteParam;       /* ID of Param representing CTE output */} CteScan;

CteScan節點的初始化過程(ExecInitCteScan函數)將首先初始化CteScanState結構,通過ctePlanld在es_subplanstates中找到對應的子計劃執行狀態樹,並儲存在CteScanState的cteplanstate欄位中。

然後通過cteParam在執行器全域狀態Estate的es_param_exec_vals欄位中擷取參數結構ParamExecData。若ParamExecData中value為NULL,表示沒有其他CteScan對此CTE初始化過儲存結構,此時會初始化CteScanState的cte_table欄位,並將leader和ParamExecData的value賦值為指向當前CteScanState的指標。若ParamExecData中的value不為NULL,則將其值陚值給leader,讓其指向第一個CteScan建立的CteScanState,而不為當前的CteScan初始化cte_table。這樣對應一個CTE全域只有一個元組緩衝結構,所有使用該CTE的CteScan都會共用該緩衝。

typedef struct CteScanState{    ScanState   ss;             /* its first field is NodeTag */    int         eflags;         /* capability flags to pass to tuplestore */    int         readptr;        /* index of my tuplestore read pointer */    PlanState  *cteplanstate;   /* PlanState for the CTE query itself */    /* Link to the "leader" CteScanState (possibly this same node) */    struct CteScanState *leader;    /* The remaining fields are only valid in the "leader" CteScanState */    Tuplestorestate *cte_table; /* rows already read from the CTE query */    bool        eof_cte;        /* reached end of CTE query? */} CteScanState;

最後。在做一些初始化工作,比如初始化處理元組的運算式上下文、子運算式、元組表、結果元組表等等。

在執行CteScan節點時,將首先査看cte_table指向的緩衝中是否緩衝元組(緩衝結構Tuplestorestate),如果有可直接擷取,否則需要先執行ctePlanld指向的子計劃擷取元組。

CteScan節點的清理過程需要清理元組緩衝結構,但只需清理leader指向自身的CteScanState。

12.WorkTableScan 節點

這個節點是和RecursiveUnion節點緊密關聯的。下面先看例子,一個RecursiveUnion查詢:

postgres=# WITH RECURSIVE t(n) AS(postgres(# VALUES(1)postgres(# UNION ALLpostgres(# SELECT n+1 FROM t WHERE n<100)postgres-# SELECT sum(n) FROM t; sum------ 5050(1 行)查詢計劃                               QUERY PLAN------------------------------------------------------------------------- Aggregate  (cost=3.65..3.66 rows=1 width=4)   CTE t     ->  Recursive Union  (cost=0.00..2.95 rows=31 width=4)           ->  Result  (cost=0.00..0.01 rows=1 width=0)           ->  WorkTable Scan on t t_1  (cost=0.00..0.23 rows=3 width=4)                 Filter: (n < 100)   ->  CTE Scan on t  (cost=0.00..0.62 rows=31 width=4)(7 行)

對於遞迴查詢求值,流程如下:

1.計算非遞迴項。對UNION(但不對UNION ALL),拋棄重複行。把所有剩餘的行包括在遞迴查詢的結果中,並且也把它們放在一個臨時的工作表中。

2.只要工作表不為空白,重複下列步驟:

  • 計算遞迴項,用當前工作表的內容替換遞迴自引用。對UNION(不是UNION ALL),拋棄重複行以及那些與之前結果行重複的行。將剩下的所有行包括在遞迴查詢的結果中,並且也把它們放在一個臨時的中間表中。

  • 用中間表的內容替換工作表的內容,然後清空中間表。

詳細可以看這裡:http://www.postgres.cn/docs/9.5/queries-with.html

這裡的工作表就是WorkTable。

WorkTableScan會與RecursiveUnion共同完成遞迴合并子査詢。RecursiveUnion會緩衝一次遞迴中的所有元組到RecursiveUnionState結構中,WorkTableScan提供了對此緩衝的掃描。

如下所示,WorkTableScan節點擴充定義了wtParam用於同RecursiveUnion節點間的通訊,而 WorkTableScanState 節點的 rustate 欄位中記錄了 RecursiveUnionState結構的指標,以便WorkTableScan在執行過程中可以從緩衝結構中擷取元組。

typedef struct WorkTableScan{    Scan        scan;    int         wtParam;        /* ID of Param representing work table */} WorkTableScan;

節點狀態:

typedef struct WorkTableScanState{    ScanState   ss;             /* its first field is NodeTag */    RecursiveUnionState *rustate;} WorkTableScanState;
13.ForeignScan節點

如果用過postgres_fdw或者dblink這些PostgreSQL提供了外部資料封裝器,那麼就大概能知道這個Scan節點的用途:掃描外部Postgresql資料表。

如果你對postgres_fdw有興趣,這裡是網址,拿去不謝:http://www.postgres.cn/docs/9.5/postgres-fdw.html

ForeignScan節點的資訊如下,主要在Scan之外擴充了外部資料相關的一些資訊。fdw_exprs和fdw_private都在外部資料封裝器的控制下,但是fdw_exprs被假定為包含運算式樹狀架構並且將由規劃器相應地進行後處理; fdw_private不會。
fdw_scan_tlist是描述由FDW返回的掃描元組的內容的目標列表;如果掃描元組與外部表格的聲明行類型匹配,則可以為NIL,這對於簡單的外部表格掃描來說是正常情況。(如果計劃節點表示外部聯結,則需要fdw_scan_tlist,因為系統目錄中沒有可用的rowtype)
fdw_scan_tlist永遠不會被執行;它只是持有描述掃描元組列中的內容的運算式樹狀架構。
fdw_recheck_quals應該包含核心系統傳遞給FDW但是沒有被添加到scan.plan.qual中的條件,也就是說,這些條件需要在FDW中做判斷(這些條件是要在recheck中做判斷的)。

typedef struct ForeignScan{    Scan        scan;    Oid         fs_server;      /* OID of foreign server */    List       *fdw_exprs;      /* expressions that FDW may evaluate */    List       *fdw_private;    /* private data for FDW */    List       *fdw_scan_tlist; /* optional tlist describing scan tuple */    List       *fdw_recheck_quals;  /* original quals not in scan.plan.qual */    Bitmapset  *fs_relids;      /* RTIs generated by this scan */    bool        fsSystemCol;    /* true if any "system column" is needed */} ForeignScan;

還有一個資料結構也要特別關注,它保持了外部資料封裝器處理常式返回函數,即它提供供PLANNER和EXECUTOR使用的回呼函數的指標。

src/include/foreign/fdwapi.htypedef struct FdwRoutine

下面是ForeignScan的狀態節點ForeignScanState,它在ScanState之外擴充了需要recheck的列表欄位fdw_recheck_quals、外部資料封裝器處理常式返回函數集合結構體FdwRoutine和外部資料封裝器狀態fdw_state。
在初始化時,ExecInitForeignScan函數除了做一般的初始化之外,還對ForeignScanState的fdwroutine欄位做了初始化,擷取函數指標和掃描關係表。

typedef struct ForeignScanState{    ScanState   ss;             /* its first field is NodeTag */    List       *fdw_recheck_quals;  /* original quals not in ss.ps.qual */    /* use struct pointer to avoid including fdwapi.h here */    struct FdwRoutine *fdwroutine;    void       *fdw_state;      /* foreign-data wrapper can keep state here */} ForeignScanState;

ForeignScan節點的執行(ExecForeignScan 函數)通過將ForeignNext傳遞給 ExecScan函數處理來實現的。ForeignNext實際則是調用fdwroutine->IterateForeignScan在外部資料源上掃描每次獲得一個元組。

關於函數ForeignRecheck,還記得上面說的fdw_recheck_quals欄位嗎?這裡調用ExecQual函數使用fdw_recheck_quals欄位中的條件來做recheck。

最後,掃描結束後,調用fdwroutine->EndForeignScan關閉掃描,並且關閉外部表格ExecCloseScanRelation(node->ss.ss_currentRelation)。

14.CustomScan節點

從Custom這個單詞我們就可以知道,這是postgres開放的自訂Scan方法的介面。這個節點只提供了一個空殼子,我們看下:

typedef struct CustomScan{    Scan        scan;    uint32      flags;          /* mask of CUSTOMPATH_* flags, see relation.h */    List       *custom_plans;   /* list of Plan nodes, if any */    List       *custom_exprs;   /* expressions that custom code may evaluate */    List       *custom_private; /* private data for custom code */    List       *custom_scan_tlist;      /* optional tlist describing scan                                         * tuple */    Bitmapset  *custom_relids;  /* RTIs generated by this scan */    const CustomScanMethods *methods;} CustomScan;

留給使用者自己去擴充,同時,CustomScanState狀態節點也是一樣,裡面只有一些函數指標和預設的一些屬性,你可以使用,也可以把CustomScanState作為你要擴充的Scan方法的State的一個屬性,可以說還是很靈活的。

因此,不多說這個了,希望能在網上看到關於這方面做擴充的例子~

Scan節點就這麼結束了。

跟我一起讀postgresql源碼(十)——Executor(查詢執行模組之——Scan節點(下))

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.