跟我一塊兒讀postgresql源碼(十六)——Executor(查詢執行模塊之——control節點(下))

5.ModifyTable節點

先看一個ModifyTable節點的例子:html

postgres=# explain update test_01 set id = 5 where name = 'xxx';
                          QUERY PLAN
---------------------------------------------------------------
 Update on test_01  (cost=0.00..23.75 rows=6 width=48)
   ->  Seq Scan on test_01  (cost=0.00..23.75 rows=6 width=48)
         Filter: ((name)::text = 'xxx'::text)
(3 rows)

你可能疑惑爲啥上面的查詢計劃裏面沒有"ModifyTable"這樣的字眼,下面是explain.c文件中的一段:sql

case T_ModifyTable:
            sname = "ModifyTable";
            switch (((ModifyTable *) plan)->operation)
            {
                case CMD_INSERT:
                    pname = operation = "Insert";
                    break;
                case CMD_UPDATE:
                    pname = operation = "Update";
                    break;
                case CMD_DELETE:
                    pname = operation = "Delete";
                    break;
                default:
                    pname = "???";
                    break;
            }
            break;

由此咱們能夠看到,對於ModifyTable節點,explain會判斷是增刪改中的哪種從而做出判斷。因此當在explain中看到INSERT、Update和Delete時,咱們就知道這是走了ModifyTable節點。數據庫

那這裏,咱們還要再解釋ModifyTable節點麼?解釋下吧:express

*      Apply rows produced by subplan(s) to result table(s),
 *      by inserting, updating, or deleting.

也就是說,我先從下層的subplan中得到rows,而後根據命令類型選擇是insert, update仍是delete操做。因此咱們能夠知道,這是一個頂層節點,它下面是查詢節點(就是SELECT)。這也符合咱們之前一直說的,全部的增刪改查其實都是SELECT!安全

typedef struct ModifyTable
{
    Plan        plan;
    CmdType     operation;      /* INSERT, UPDATE, or DELETE */
    bool        canSetTag;      /* do we set the command tag/es_processed? */
    Index       nominalRelation;    /* Parent RT index for use of EXPLAIN */
    List       *resultRelations;    /* integer list of RT indexes */
    int         resultRelIndex; /* index of first resultRel in plan's list */
    List       *plans;          /* plan(s) producing source data */
    List       *withCheckOptionLists;   /* per-target-table WCO lists */
    List       *returningLists; /* per-target-table RETURNING tlists */
    List       *fdwPrivLists;   /* per-target-table FDW private data lists */
    List       *rowMarks;       /* PlanRowMarks (non-locking only) */
    int         epqParam;       /* ID of Param for EvalPlanQual re-eval */
    OnConflictAction onConflictAction;  /* ON CONFLICT action */
    List       *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs  */
    List       *onConflictSet;  /* SET for INSERT ON CONFLICT DO UPDATE */
    Node       *onConflictWhere;    /* WHERE for ON CONFLICT UPDATE */
    Index       exclRelRTI;     /* RTI of the EXCLUDED pseudo relation */
    List       *exclRelTlist;   /* tlist of the EXCLUDED pseudo relation */
} ModifyTable;

因爲ModifyTable節點涉及的操做比較多,這裏稍微解釋下ModifyTable中的一些字段。併發



withCheckOptionLists字段

這個和視圖相關,咱們知道建立視圖有這樣的用法:函數

CREATE VIEW xxx_view AS query WITH CHECK OPTION

在postgres中,對建立語句中帶有WITH CHECK OPTION的VIEW,在經過視圖進行的操做(增刪改),必須也能經過該視圖看到操做後的結果。post

也就是說:lua

對於INSERT,那麼加的這條記錄在視圖查詢後必須能夠看到。code

對於UPDATE,修改完的結果也必須能經過該視圖看到。

對於DELETE,只能刪除視圖裏有顯示的記錄。

所以對這一類操做,咱們在操做表/視圖的時候,要在(INSERT/UPDATE/DELETE的)WHERE條件中加上WITH OPTION中的條件。



returningLists字段

這個很簡單,由於postgres的語法中有相似如下的用法:

DELETE FROM xxx_table WHERE condition RETURNING xxx;
UPDATE xxx_table SET a = '123'   WHERE condition RETURNING xxx;
INSERT INTO xxx_table VALUES (somevalues) RETURNING xxx;

是的,postgres的RETURNING子句能夠返回修改的行,因此對於含有RETURNING子句的查詢,除了在對錶中的數據進行INSERT/UPDATE/DELETE,還要額外返回一些行。即還要有額外的輸出。



fdwPrivLists字段

Postgres支持訪問外部數據庫的嘛,因此這個字段提供對fdw的處理的支持。



rowMarks字段

這個和行鎖相關,針對SELECT的LOCK子句:

FOR lock_strength [ OF table_name [, ...] ] [ NOWAIT | SKIP LOCKED ]

具體見這裏:http://www.postgres.cn/docs/9.5/sql-select.html



onConflictAction、arbiterIndexes、arbiterIndexes和onConflictWhere字段

是的,對於INSERT操做,咱們有如下語法(用於支持INSERT中發生的衝突):

INSERT INTO table_name VALUES (somevalues) ON CONFLICT [ conflict_target ] conflict_action

而且 conflict_action 是如下之一:

    DO NOTHING
    DO UPDATE SET { column_name = { expression | DEFAULT } |
                    ( column_name [, ...] ) = ( { expression | DEFAULT } [, ...] ) |
                    ( column_name [, ...] ) = ( sub-SELECT )
                  } [, ...]
              [ WHERE condition ]

這樣一看,很容易對的上了。



exclRelRTI、exclRelTlist字段

對於建表語句有如下子句:

EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ]

EXCLUDE子句指定一個排除約束,它保證若是任意兩行在指定列或表達式上使用指定操做符進行比較,不是全部的比較都將會返回TRUE。具體見這裏

所以,你能夠把這個字段看作是一個約束字段,在作更新操做時須要判斷。



typedef struct ModifyTableState
{
    PlanState   ps;             /* its first field is NodeTag */
    CmdType     operation;      /* INSERT, UPDATE, or DELETE */
    bool        canSetTag;      /* do we set the command tag/es_processed? */
    bool        mt_done;        /* are we done? */
    PlanState **mt_plans;       /* subplans (one per target rel) */
    int         mt_nplans;      /* number of plans in the array */
    int         mt_whichplan;   /* which one is being executed (0..n-1) */
    ResultRelInfo *resultRelInfo;       /* per-subplan target relations */
    List      **mt_arowmarks;   /* per-subplan ExecAuxRowMark lists */
    EPQState    mt_epqstate;    /* for evaluating EvalPlanQual rechecks */
    bool        fireBSTriggers; /* do we need to fire stmt triggers? */
    OnConflictAction mt_onconflict;     /* ON CONFLICT type */
    List       *mt_arbiterindexes;      /* unique index OIDs to arbitrate
                                         * taking alt path */
    TupleTableSlot *mt_existing;    /* slot to store existing target tuple in */
    List       *mt_excludedtlist;       /* the excluded pseudo relation's
                                         * tlist  */
    TupleTableSlot *mt_conflproj;       /* CONFLICT ... SET ... projection
                                         * target */
} ModifyTableState;

那麼對於ModifyTableState的一些字段,咱們參照ModifyTable節點的解釋,也能理解的差很少,這裏很少說了。

下面進入正題:



ModifyTable節點的初始化由ExecInitModifyTable函數來作。該函數除了作一些基礎的初始化操做外,針對我上面提到的那些字段,作了設置和初始化。說細一點的話就是:

  • (1)調用ExecInitNode函數對ModifyTable節點中的plans列表中的subplans節點進行初始化並將其結果保存到ModifyTableState結構的mt_plans字段中。在這一步中,同時也順便作了這些事:驗證了查詢所涉及的target relations是不是合法的;打開這些target relations上的index,由於對於UPDATE/INSERT操做,咱們同時還要對相應的索引進行操做(DELETE操做不刪除索引,DELETE後遺留的index留給VACUUM去清理)。

  • (2)根據ModifyTable節點中的withCheckOptionLists字段初始化上面提到的WITH CHECK OPTION(若是存在的話)。初始化後保存在ModifyTableState結構的resultRelInfo字段的成員變量ri_WithCheckOptions和ri_WithCheckOptionExprs中。

  • (3)根據ModifyTable節點中的returningLists字段初始化上面提到的RETURNING子句(若是存在的話)並根據此構造返回的結果集的類型。若是returningLists字段爲空,說明沒有RETURNING子句。那麼返回結果集的類型設置爲NULL。

  • (4)若是存在ON CONFLICT DO UPDATE字段那麼爲他初始化目標列表Target List、投影條件resultRelInfo和過濾條件qual,結果保存在ModifyTableState結構的相應字段中。

  • (5)處理ModifyTable節點中的RowMark字段,結果保存在ModifyTableState結構的mt_arowmarks字段中。

  • (6)初始化junk filter。這個junk filter的由來是由於在plan階段,咱們會產生一些"中間信息"放在tuple中供Excutor使用。好比ctid,用來定位該元組放到磁盤文件的位置。可是當咱們將元組寫到磁盤時,咱們不須要保存這個信息。那麼這個信息至關因而冗餘的了,咱們須要用這個JunkFilter將其過濾和刪除。

  • (7)咱們知道咱們在INSERT/UPDATE/DELETE時可能會涉及到trigger,這裏設置trigger相關的slot,供後續函數調用。

  • (8)若是本ModifyTable節點不是頂層ModifyTable節點的話(上層還有ModifyTable節點),設置全局狀態結構estate->es_auxmodifytables屬性,作上標記。



ModifyTable節點的執行由ExecModifyTable函數執行。具體的來講:

  • (1)首先咱們要記得可能有BEFORE STATEMENT triggers這玩意兒,顧名思義,就是要在STATEMENT執行以前執行這個trigger。若是存在,在進入正式的處理以前咱們先要調用fireBSTriggers函數來處理它。

  • (2)接下來是一個大for循環。在這個for循環裏面,程序調用ExecProcNode函數循環地從下層節點中讀取元組。須要注意的是這個循環裏面相似Append節點的操做,在讀取完第一個subplans節點中的元組後,會依次讀取後續subplan中的元組,直到所有讀取完畢。咱們之前說過postgres是一次讀取一個元組並處理一個元組的。這裏也不例外,每讀取一個元組後根據操做的類型分別調用ExecInsert/ExecUpdate/ExecDelete函數去處理。

  • (3)善始善終,既然可能有BEFORE STATEMENT triggers,那麼也可能有AFTER STATEMENT triggers,這裏調用fireASTriggers函數來處理它。

那麼咱們應該對ExecInsert/ExecUpdate/ExecDelete函數感興趣了。下面咱們開始討論他們。

1.ExecInsert

對於ExecInsert函數的話,主要是兩件事:將元組插入到目標表target relation中同時將對應的索引項插入到相關的索引表index relations(可能有多個索引要處理)中。

  • (1)首先要將須要插入的tuple從slot中取出,本地化。why?由於這個slot在隨後的操做heap_insert函數中可能不安全,所以將其提早取出來。這個工做由ExecMaterializeSlot函數完成。

  • (2)從全局狀態中estate->es_result_relation_info獲取信息,判斷result relation是否須要一個oid。若是須要,則先將tuple中的oid字段設爲0。

  • (3)處理BEFORE ROW INSERT Triggers。這裏咱們要注意這個觸發器是ROW級別的,而BEFORE STATEMENT triggers是語句級別的,他們不同。

  • (4)處理INSTEAD OF ROW INSERT Triggers。若是存在則調用ExecIRInsertTriggers函數去處理並直接返回,不進行INSERT操做。

  • (5)處理foreign table的狀況,爲其初始化ri_FdwRoutine。調用foreign server的API去處理該條元組的插入並獲取返回的slot

  • (6)處理RLS INSERT WITH CHECK中的條件(ExecWithCheckOptions函數,"Row-Level Security (RLS) support" 是9.5版本的主要特性之一,提供了基於行的安全策略,限制數據庫用戶的查看錶數據權限)和惟一性約束(ExecConstraints函數)ON ONCONFLICT OPTION。

  • (7)若是存在ON ONCONFLICT OPTION條件,則先得到speculative insertion lock,調用heap_insert函數將元組插入到堆表中。若是插入成功,不發生衝突則正常釋放該lock。不然強制釋放lock,並執行ON ONCONFLICT DO UPDATE(若是有的話)。

  • (8)不存在(7)中的條件,咱們正常地調用heap_insert函數將元組插入到堆表中。同時調用ExecInsertIndexTuples函數插入相應的索引元組。

  • (9)調用ExecARInsertTriggers函數處理AFTER ROW INSERT Triggers。相似(3)的處理。

  • (10)還記得上面提到的CREATE VIEW中的WITH CHECK OPTION麼?這裏調用ExecWithCheckOptions函數作處理,不知足則報錯退出。

  • (11)若是存在RETURNING子句,咱們調用ExecProcessReturning函數處理之。

2.ExecDelete

ExecDelete函數相對簡單,他只須要將元組刪除便可,不須要針對索引作任何操做。

  • (1)從全局狀態中estate->es_result_relation_info獲取信息。

  • (2)處理BEFORE ROW DELETE Triggers。這裏咱們要注意這個觸發器是ROW級別的,而BEFORE STATEMENT triggers是語句級別的,他們不同。

  • (3)處理INSTEAD OF ROW DELETE Triggers。若是存在則調用ExecIRDeleteTriggers函數去處理並直接返回,不進行INSERT操做。

  • (4)處理foreign table的狀況,爲其初始化ri_FdwRoutine。調用foreign server的API去處理該條元組的刪除並獲取返回的slot。

  • (5)咱們正常地調用heap_delete函數執行DELETE操做。若是返回值不是HeapTupleMayBeUpdated則說明操做失敗,根據失敗的錯誤代碼執行相應的處理。

  • (6)調用ExecARDeleteTriggers函數處理AFTER ROW DELETE Triggers。相似(2)的處理。

  • (7)若是存在RETURNING子句,咱們調用ExecProcessReturning函數處理之。

3.ExecUpdate

ExecUpdate函數實際上執行的是"INSERT"操做。由於postgres內部是MVCC機制,多版本併發控制。舊的元組實際上沒有刪除,只是再也不引用。同時,UPDATE操做在數據庫內部也是要在"transaction"中的,不然postgres會不停的將新增的updated元組當作是須要update的元組,循環下去。

  • (1)判斷當前是否屬於BootstrapProcessing模式,在該模式下全部的transaction id都被設置爲1。這個時候才能保證不循環更新。

  • (2)首先要將須要插入的tuple從slot中取出,本地化。why?由於這個slot在隨後的操做heap_update函數中可能不安全,所以將其提早取出來。這個工做由ExecMaterializeSlot函數完成。

  • (3)處理BEFORE ROW UPDATE Triggers。

  • (4)處理INSTEAD OF ROW UPDATE Triggers。若是存在則調用ExecIRUpdateTriggers函數去處理並直接返回,不進行INSERT操做。

  • (5)處理foreign table的狀況,爲其初始化ri_FdwRoutine。調用foreign server的API去處理該條元組的更新並獲取返回的slot。

  • (6)處理RLS UPDATE WITH CHECK中的條件(ExecWithCheckOptions函數)和惟一性約束(ExecConstraints函數)ON ONCONFLICT OPTION。

  • (7)咱們正常地調用heap_update函數執行UPDATE、操做。若是返回值不是HeapTupleMayBeUpdated則說明操做失敗,根據失敗的錯誤代碼執行相應的處理。若是成功,則調用ExecInsertIndexTuples函數向索引中插入索引元組。

  • (8)調用ExecARUpdateTriggers函數處理AFTER ROW UPDATE Triggers。

  • (9)針對表的上層VIEW再次執行WITH CHECK OPTION。

  • (10)若是存在RETURNING子句,咱們調用ExecProcessReturning函數處理之。



ModifyTable節點的清理簡單些了(ExecEndModifyTable函數)。除了常規的清理工做,清理可能存在FDW結構,清理初始化中額外初始化的那些subplans節點。



control節點到此結束。

相關文章
相關標籤/搜索