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

來源:互聯網
上載者:User

標籤:select   引用   node   text   pen   apply   gre   first   exec   

5.ModifyTable節點

先看一個ModifyTable節點的例子:

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檔案中的一段:

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節點嗎?解釋下吧:

 *      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,在通過視圖進行的操作(增刪改),必須也能通過該視圖看到操作後的結果。

也就是說:

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

對於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)處理WITH CHECK OPTION中的條件(ExecWithCheckOptions函數)和唯一性限制式(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)處理WITH CHECK OPTION中的條件(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節點到此結束。

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.