跟我一起读postgresql源码(十六)——Executor(查询执行模块之——control节点(下))
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)处理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节点到此结束。
跟我一起读postgresql源码(十六)——Executor(查询执行模块之——control节点(下))的更多相关文章
- 跟我一起读postgresql源码(十二)——Executor(查询执行模块之——Materialization节点(下))
接前文,我们继续说剩下的4个Materialization节点. 7.SetOp节点 SetOp节点用于处理集合操作,对应于SQL语句中的EXCEPT.INTERSECT两种集合操作,至于另一种集合操 ...
- 跟我一起读postgresql源码(十五)——Executor(查询执行模块之——control节点(上))
控制节点 控制节点用于完成一些特殊的流程执行方式.由于PostgreSQL为査询语句生成二叉树状的査询计划,其中大部分节点的执行过程需要两个以内的输入和一个输出.但有一些特殊的功能为了优化的需要,会含 ...
- 跟我一起读postgresql源码(十四)——Executor(查询执行模块之——Join节点(下))
3.HashJoin 节点 postgres=# explain select a.*,b.* from test_dm a join test_dm2 b on a.xxx = b.xxx; QUE ...
- 跟我一起读postgresql源码(十)——Executor(查询执行模块之——Scan节点(下))
接前文跟我一起读postgresql源码(九)--Executor(查询执行模块之--Scan节点(上)) ,本篇把剩下的七个Scan节点结束掉. T_SubqueryScanState, T_Fun ...
- 跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上))
从前面介绍的可优化语句处理相关的背景知识.实现思想和执行流程,不难发现可优化语句执行的核心内容是对于各种计划节点的处理,由于使用了节点表示.递归调用.统一接口等设计,计划节点的功能相对独立.代码总体流 ...
- 跟我一起读postgresql源码(十一)——Executor(查询执行模块之——Materialization节点(上))
物化节点 顾名思义,物化节点是一类可缓存元组的节点.在执行过程中,很多扩展的物理操作符需要首先获取所有的元组后才能进行操作(例如聚集函数操作.没有索引辅助的排序等),这时要用物化节点将元组缓存起来.下 ...
- 跟我一起读postgresql源码(十三)——Executor(查询执行模块之——Join节点(上))
Join节点 JOIN节点有以下三种: T_NestLoopState, T_MergeJoinState, T_HashJoinState, 连接类型节点对应于关系代数中的连接操作,PostgreS ...
- 跟我一起读postgresql源码(六)——Executor(查询执行模块之——查询执行策略)
时光荏苒,岁月如梭.楼主已经很久没有更新了.之前说好的一周一更的没有做到.实在是事出有因,没能静下心来好好看代码.当然这不能作为我不更新的理由,时间挤挤还是有的,拖了这么久,该再写点东西了,不然人就怠 ...
- 跟我一起读postgresql源码(八)——Executor(查询执行模块之——可优化语句的执行)
2.可优化语句的执行 可优化语句的共同特点是它们被查询编译器处理后都会生成査询计划树,这一类语句由执行器(Executor)处理.该模块对外提供了三个接口: ExecutorStart.Executo ...
随机推荐
- SRM13
由于种种原因,好像出了点锅……? 好在问题不是很大. 得分比我估的要低啊. 木之本樱 计算几何送分题 就是叫你求一共有多少组四线共点,O(n^4)暴力可以过初.枚举两条线,求出交点之后求有多少条直线过 ...
- BZOJ:3911: SGU383 Caravans(三角剖分)
原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3911 直接求最小生成树显然边太多,考虑少用点边. 连出来的边肯定是没相交的,我们需要做一下 ...
- BZOJ 1207: [HNOI2004]打鼹鼠【妥妥的n^2爆搜,dp】
1207: [HNOI2004]打鼹鼠 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 3259 Solved: 1564[Submit][Statu ...
- 深入设计电子计算器(一)——CPU框架及指令集设计
版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖.如要转贴,必须注明原文网址 http://www.cnblogs.com/Colin-Cai/p/8278418.html 作者:窗户 Q ...
- TypeScript笔记 4--变量声明
在上一篇:基础变量中我们在声明变量时使用了关键字let,这和JS中的var有点类似. 语法 基本语法:let 变量名:类型.当然类型不是必须的. let x:number; let y:string ...
- LockSupport理解
一.背景 在看并发包源码的时候看见过LockSupport,今天恰巧看到LockSupport字眼,于是看下jdk1.7中的源码结构.想着它应该是运用多线程的锁工具的,到底似乎怎么实现的呢? 二.使用 ...
- 冒泡排序和选择排序-java
冒泡排序 假设有一数组int [] arr = {9,5,4,10,2};原理是第一个元素和第二个比较,如果前者大于后者便交换位置,然后第二个元素和第三个元素比较,如果前者大于后者便交换位置.以此类 ...
- Spark算子--partitionBy
转载请标明出处http://www.cnblogs.com/haozhengfei/p/923b11fce561e82748baa016bcfb8421.html partitionBy--Trans ...
- 邓_ Php·笔记本[照片]
-------------------------------------------------------------------------------------------- [PHP] - ...
- 直接请求转发(Forward)和间接请求转发(Redirect)两种区别?
用户向服务器发送了一次HTTP请求,该请求肯能会经过多个信息资源处理以后才返回给用户,各个信息资源使用请求转发机制相互转发请求,但是用户是感觉不到请求转发的.根据转发方式的不同,可以区分为直接请求转发 ...