跟我一起读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 NOTHINGDO 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节点到此结束。

转载于:https://www.cnblogs.com/flying-tiger/p/8418293.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/571190.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java获取对象的子_java – 如何根据子对象字段获取父对象

家长班&#xff1a;public class Person {String firstName;String lastName;Long id;List phoneNumber new ArrayList<>();int age;public Person(String firstName, String lastName, int age, Long id, List phone) {super();this.firstName firstName;this.lastNam…

vscode格式化关于符合eslint检测语法配置

.js文件插件&#xff1a;JavaScript Standard Style配置&#xff1a;解决ES6语法格式化 {"files.associations": {"*.js": "javascriptreact"} }.vue文件 配置&#xff1a; {"vetur.format.defaultFormatter.js": "vscode-typesc…

二、配置数据源、SessionFactory、domain对象

1.在applicationContext.xml中配置数据源 <?xml version"1.0" encoding"utf-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:context"h…

IDEA建立Spring MVC Hello World 详细入门教程

引子&#xff0c;其实从.NET转Java已经有几个月时间了&#xff0c;项目也做了不少&#xff0c;但是很多配置都是根据公司模板或者网上教程比忽略画瓢&#xff0c;对其中最简单的配置和设置并不完全理解&#xff0c;依旧是小白用户。最近项目不忙&#xff0c;重新梳理了一下Spri…

java offsetdatetime_Java OffsetDateTime withHour()用法及代码示例

Java中OffsetDateTime类的withHour()方法返回此OffsetDateTime的副本&#xff0c;其中一天中的小时数按照参数中的指定进行了更改。用法:public OffsetDateTime withHour(int hour)参数&#xff1a;此方法接受单个参数hour&#xff0c;该参数指定要在结果中设置的一天中的小时&…

2048小游戏代码解析 C语言版

2048小游戏&#xff0c;也算是风靡一时的益智游戏。其背后实现的逻辑比较简单&#xff0c;代码量不算多&#xff0c;而且趣味性强&#xff0c;适合作为有语言基础的童鞋来加强编程训练。本篇分析2048小游戏的C语言实现代码。 前言 游戏截图&#xff1a; 游戏实现原理&#xff1…

java中Decimaformat_Java中 DecimalFormat 用法详解

对Java中 DecimalFormat 的所有基础用法进行了一个汇总。DecimalFormat 类主要靠 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充&#xff0c;# 表示只要有可能就把数字拉上这个位置。下面的例子包含了差不多所有的基本用法。import java.text.DecimalForm…

java的栈堆的理解_理解堆与栈 - 一步一个脚印 - BlogJava

一、引言&#xff1a;一直不太明白堆与栈到底是什么&#xff0c;是指一块内存区呢&#xff0c;还是指一种数据结构&#xff1f;编写程序中总提到内存分配的栈与堆的区别&#xff0c;静态与动态分配等&#xff0c;总也弄不明白&#xff0c;隐约知道一点&#xff0c;但总分不清堆…

【递归与递推】青蛙过河

题目描述 有一条河&#xff0c;左边一个石墩(A区)上有编号为1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;…&#xff0c;n的n只青蛙&#xff0c;河中有k个荷叶(C区)&#xff0c;还有h个石墩(D区)&#xff0c;右边有一个石墩(B区)&#xff0c;如下图2—5所示。n只青蛙…

人民币读法的java程序_Java浮点数转人民币读法

packagetheTest;public classNumToRmb {private String[] hanArr{"零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};private String[] unitArr{"仟",&quo…

python arp欺骗

使用python构造一个arp欺骗脚本 import os import sys from scapy.all import * import optparse def main():usage"usage:[-i interface] [-t IP to attack] [-g Gateway IP]"parseroptparse.OptionParser(usage)parser.add_option(-i,destinterface,helpselect int…

java 如何使用dylib,如何在应用程序中使用dylib文件?

I have created lib.dylib dynamic library. I want to use that library in my application.What are the Build setting and build phase settings are required?Steps to use the library in objective-c.解决方案so there are 2 ways...1) if the Dyld is available at li…

python中的基本数据结构

要点概论&#xff1a; 1.了解序列 2.掌握列表 3.掌握元组 4.掌握字符串 5.掌握字典 6.掌握json 1.序列 在python中&#xff0c;最基本的数据结构是序列。 python提供了列表&#xff0c;元组&#xff0c;字符串等序列类型&#xff0c;可以进行某些特定的操作&#xff0c;这些操作…

java对字符串归一化_搜索引擎中的字符串归一化 | 学步园

搜索引擎中对于Q查询&#xff0c;都会涉及到字符串归一化这个步骤&#xff0c;以提高结果召回率。字符串的归一化包括三个方面&#xff1a;繁体转简体&#xff1b;全角字符转半角&#xff1b;大写字母转小写。建倒排时&#xff0c;会先对字符串做归一化处理&#xff0c;然后再分…

java对外sdk提供接口_Android SDK封装,对外提供接口

项目中需要把连接服务器的部分做成一个service并生成一个jar模块。其他产品就可通过这个包来快速的开发连接服务器的应用软件。做成一个service的优点是&#xff1a;1&#xff0e; 在后台运行&#xff0c;可以一直保持与服务器的连接2&#xff0e; 服务可以只对外提供接口&…

hdu3265一种错误的做法

题目链接 这是求面积并的题目&#xff0c;刚开始我的思路是将挖去的矩形的入边和出边覆盖效果颠倒&#xff0c; 即入边-1&#xff0c;出边1&#xff0c;后来调试到爆炸&#xff0c;发现这是错误的做法。。原因就是对最简单 的面积并问题没有搞清楚。刚开始接触扫描线的时候我就…

java综合案例_综合实例 - Java House - BlogJava

packagebedeck;publicclassBedeckDome {/** 实例变量* *///类中不能实例化对象publicStringstr1;//无初始值&#xff0c;可以被任何类访问protectedStringstr2;//无初始化值&#xff0c;可以被同一包中的所有类访问&#xff0c;可以被所有子类访问privateStringstr3;//无初始化…

53-C++ CH08 01

http://lx.lanqiao.cn/problem.page?gpidT407 算法训练 C CH08 01 时间限制&#xff1a;1.0s 内存限制&#xff1a;256.0MB问题描述已知一个有理数类Zrf_Ratio&#xff0c;实现如下的操作符重载形式&#xff1a;friend std::ostream& operator<<(std::ostream&am…

Linux文件系统选择

自己想做的&#xff0c;刘爱贵在2010年就做完了(⊙ω⊙) http://blog.csdn.net/liuaigui/article/details/5521024 通过综合使用多种标准文件系统Benchmarks对Ext3, Ext4, Reiserfs, XFS, JFS, Reiser4的性能测试对比&#xff0c;对不同应用选择合适的文件系统给出以下方案&…

java里shake是什么意思_shake是什么意思_shake在线翻译_英语_读音_用法_例句_海词词典...

使振作起来 shock sb into activityshake sth ⇔ upShake up the salad-dressing before you put it on.加色拉调料之前先把它摇匀。shake sth ⇔ upMother ran round the room shaking up all the cushions when the door-bell rang.母亲正在屋里跑来跑去忙着抖松所有的坐垫,这…