先看一個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中的一些字段。併發
這個和視圖相關,咱們知道建立視圖有這樣的用法:函數
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中的條件。
這個很簡單,由於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,還要額外返回一些行。即還要有額外的輸出。
Postgres支持訪問外部數據庫的嘛,因此這個字段提供對fdw的處理的支持。
這個和行鎖相關,針對SELECT的LOCK子句:
FOR lock_strength [ OF table_name [, ...] ] [ NOWAIT | SKIP LOCKED ]
具體見這裏:http://www.postgres.cn/docs/9.5/sql-select.html
是的,對於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 ]
這樣一看,很容易對的上了。
對於建表語句有如下子句:
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函數感興趣了。下面咱們開始討論他們。
對於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函數處理之。
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函數處理之。
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節點到此結束。