时光荏苒,岁月如梭。楼主已经很久没有更新了。之前说好的一周一更的没有做到。实在是事出有因,没能静下心来好好看代码。当然这不能作为我不更新的理由,时间挤挤还是有的,拖了这么久,该再写点东西了,不然人就怠懒了。不过这回,我准备写的精简些,一方面我想偷点懒省点时间,二来毕竟写太长大家也不一定爱看。

之前我说过的查询分析,查询重写和查询规划都是相当于是对查询的"编译"。那么编译完了就应该按照既定的策略去执行它。本篇就来介绍查询执行模块的代码(Executor),欢迎拍砖。

这部分我主要从以下五个部分介绍查询执行模块(很可能要分成四到五篇文章来阐述,毕竟是比查询规划还要复杂的模块):

1.查询优化策略
2.非可优化语句的执行
3.可优化语句的执行
4.计划节点
5.其它子功能介绍

查询执行器的框架结构如下图所示。

在exec_simple_query函数中,调用查询编译模块之后,就进入了查询执行器模块。在该模块中,就是按照前一阶段查询规划模块锁生成的查询计划,有机第调用存储、索引,并发等模块来完成数据的读取或者修改的过程。

在本模块中,总共下属四个子模块,分别是:Portal、ProcessUtility、Executor和其他特定子功能模块。

我们知道,查询规划阶段将查询分为两种类型,在查询执行模块中,先由Portal模块识别查询类型(有计划树和无计划树),根据查询类型分别指派Executor模块和ProcessUtility模块进行处理。

这两个子模块的处理逻辑相差很大,执行过程和先关数据结构差异也很大。

对于Executor模块,它根据输入的查询计划树按部就班地处理数据表中元组的增删改查(DML)操作,它的执行逻辑是统一的(所有的增删改查最后都归结为SELECT,只是分别在SELECT的基础上进行一些额外的操作)。其主要代码放在src/backend/executor下。

而对于ProcessUtility模块,由于处理的是除了增删改查之外的所有其他操作,而这些操作往往差异很大,例如数据定义操作(DDL),事务的处理以及游标用户角色定义这些,因此在ProcessUtility模块中,为每种操作单独地设计了子过程(函数)去处理。主要代码在src/backend/commands下。

而剩下的我说的特定功能子模块,是指一些功能相对独立和单一并且在整个查询过程中会反复被调用的函数(我更愿意称他们为工具函数),例如各种辅助的子系统,表达式的计算,投影运算以及元组操作这些。


1.查询优化策略

在进入这一模块之前,我已经简要说明了Executor模块和ProcessUtility模块这两个主要的执行分支。这里要提到两个概念:

可优化语句和非可优化语句

可优化语句说白了就是DML语句,这些语句的特点就是都要查询到满足条件的元组。这类查询都在查询规划阶段生成了规划树,而规划树的生成过程中会根据查询优化理论进行重写和优化以提高查询速度,因此称作可优化语句。

那反过来讲,那些没有生成执行计划树的功能性操作就是非可优化语句了。这里只是提一下这个概念,在后面的介绍中会用。

1.1五种执行策略

上面提到,一条简单的SQL语句会被查询编译器转化为一个执行计划树或者一个非计划树操作。而一条复杂的SQL语句往往同时带有DDL和DML语句,即它会被转换为一个可执行计划树和非执行计划树操作的序列。而可执行计划树和非可执行计划树是由不同的子模块去处理的。这样就有了三种不同的情况,需要三种不同的策略去应对。

然而除此之外,我们还有一种额外的情况需要考虑到:有些SQL语句虽然可以被转换为一个原子操作,但是其执行过程中由于各种原因需要能够缓存语句执行的结果,等到整个语句执行完毕在返回执行结果。

具体的说:

  • 1 对于可优化语句,当执行修改元组操作时,希望能够返回被修改的元组(例如带RETURNING子句的DELETE),由于原子操作的处理过程不能被可能有问题的输出过程终止,因此不能边执行边输出,因此需要一个缓存结构来临时存放执行结果;

  • 2 某些非优化语句是需要返回结果的(例如SHOW,EXPLAIN) ,因此也需要一个缓存结构暂存处理结果。

此外,对于带有INSERT/UPDATE/DELETE的WITH子句,会在CTE中修改数据,和一般的CTE不一样。我们也需要进行特事特办,特殊处理,这是第五种情况。

因此,综合上面所说的,我们需要有五种处理策略来解决,分别如下:

  • 1)PORTAL_ONE_SELECT:处理单个的SELECT语句,调用Executor模块;

  • 2)PORTAL_ONE_RETURNING:处理带RETURNING的UPDATE/DELETE/INSERT语句,调用Executor模块;

  • 3)PORTAL_UTIL_SELECT:处理单个的数据定义语句,调用ProcessUtility模块;

  • 4)PORTAL_ONE_MOD_WITH:处理带有INSERT/UPDATE/DELETE的WITH子句的SELECT,其处理逻辑类似PORTAL_ONE_RETURNING。调用Executor模块;

  • 5)PORTAL_MULTI_QUERY:是前面几种策略的混合,可以处理多个原子操作。

1.2 策略的实现

执行策略选择器的工作是根据查询编译阶段生成的计划树链表来为当前的查询选择五种执行策略中的一种。在这个过程中,执行策略选择器会使用数据结构PortalData来存储查询计划树链表以及最后选中的执行策略等信息。

对于Portal这一数据结构(定义在src/include/utils/portal.h里),我把重要的字段信息也贴在下面吧:

typedef struct PortalData
{
/* Bookkeeping data */
const char *name; /* portal's name */
......
/* The query or queries the portal will execute */
const char *sourceText; /* text of query (as of 8.4, never NULL) */
const char *commandTag; /* command tag for original query */
List *stmts; /* PlannedStmts and/or utility statements */
......
ParamListInfo portalParams; /* params to pass to query */ /* Features/options */
PortalStrategy strategy; /* see above */ /* If not NULL, Executor is active; call ExecutorEnd eventually: */
QueryDesc *queryDesc; /* info needed for executor invocation */ /* If portal returns tuples, this is their tupdesc: */
TupleDesc tupDesc; /* descriptor for result tuples */
......
} PortalData;

对于查询执行器来说,在执行一个SQL语句时都会以一个Portal作为输入数据,在Portal中存放了与执行该SQL相关的所有信息,例如查询树、计划树和执行状态等。

Portal结构和与之相关的主要字段的结构如下所示:

这里仅仅给出了两种可能的原子操作PlannedStmt和Query,这两者都能包含查询计划树,用于保存含有查询的操作。当然有些含有查询计划树的原子操作不一定是SELECT语句,例如游标的声明(utilityStmt字段不为空),SELECT INTO语句(intoClause字段不为空)等等。

那么我们很容易想到,postgres是不是就是根据原子操作的命令类型和原子操作的个数来确定合适的执行策略当然呢?

YES!但是不完全

命令的类型就如下几种:

//  src/include/nodes/nodes.h
typedef enum CmdType
{
CMD_UNKNOWN,
CMD_SELECT, /* select stmt */
CMD_UPDATE, /* update stmt */
CMD_INSERT, /* insert stmt */
CMD_DELETE,
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
* etc. */
CMD_NOTHING /* dummy command for instead nothing rules
* with qual */
} CmdType;

那么策略到底如何呢?无非就是根据命令类型,原子操作个数以及查询树、计划树上的某些字段(比如hasModifyingCTE、utilityStmt等等)这些做判断了,具体的我就不写了,太占版面了,大家也不愿意看的。

执行这一任务的函数是ChoosePortalStrategy,在src/backend/tcop/pquery.c文件中。函数逻辑比较清晰,大家有兴趣可以瞅瞅。

1.3 Portal的执行过程

好了,终于到了这里,我们讲一讲一个Portal是如何执行的吧。

所有的SQL语句的执行都必须从一个Portal开始,所有的Portal流程都必须要进过下面这个流程:

该流程都在exec_simple_query函数内部进行。过程大致如下:

  • 1)调用函数CreatePortal创建一个“clean”的Portal,它的内存上下文,资源跟踪器清理函数都已经设置好,但是sourceText,stmts字段还未设置;
  • 2)调用函数PortalDefineQuery函数为刚刚创建的Portal设置sourceText,stmt等,并且设置Portal的状态为PORTAL_DEFINED;
  • 3)调用函数PortalStart对定义好的Portal进行初始化:
a.调用函数ChoosePortalStrategy为portal选择策略;
b.如果选择的是PORTAL_ONE_SELECT,则调用CreateQueryDesc为Portal创建查询描述符;
c.如果选择的是PORTAL_ONE_RETURNING或者PORTAL_ONE_MOD_WITH,则调用ExecCleanTypeFromTL为portal创建返回元组的描述符;
d.对于PORTAL_UTIL_SELECT则调用UtilityTupleDescriptor为Portal创建查询描述符;
e.对于PORTAL_MULTI_QUERY这里则不做过多操作;
f.将Portal的状态设置为PORTAL_READY。
  • 4)调用函数PortalRun执行portal,这就按照既定的策略调用相关执行部件执行Portal;
  • 5)调用函数PortalDrop清理Portal,释放资源。

最后画一张图来解释整个流程吧:

下一篇,我们细细讲讲可优化语句和非可优化语句的执行~

跟我一起读postgresql源码(六)——Executor(查询执行模块之——查询执行策略)的更多相关文章

  1. 跟我一起读postgresql源码(八)——Executor(查询执行模块之——可优化语句的执行)

    2.可优化语句的执行 可优化语句的共同特点是它们被查询编译器处理后都会生成査询计划树,这一类语句由执行器(Executor)处理.该模块对外提供了三个接口: ExecutorStart.Executo ...

  2. 跟我一起读postgresql源码(十)——Executor(查询执行模块之——Scan节点(下))

    接前文跟我一起读postgresql源码(九)--Executor(查询执行模块之--Scan节点(上)) ,本篇把剩下的七个Scan节点结束掉. T_SubqueryScanState, T_Fun ...

  3. 跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上))

    从前面介绍的可优化语句处理相关的背景知识.实现思想和执行流程,不难发现可优化语句执行的核心内容是对于各种计划节点的处理,由于使用了节点表示.递归调用.统一接口等设计,计划节点的功能相对独立.代码总体流 ...

  4. 跟我一起读postgresql源码(七)——Executor(查询执行模块之——数据定义语句的执行)

    1.数据定义语句的执行 数据定义语句(也就是之前我提到的非可优化语句)是一类用于定义数据模式.函数等的功能性语句.不同于元组增删査改的操作,其处理方式是为每一种类型的描述语句调用相应的处理函数. 数据 ...

  5. 跟我一起读postgresql源码(十一)——Executor(查询执行模块之——Materialization节点(上))

    物化节点 顾名思义,物化节点是一类可缓存元组的节点.在执行过程中,很多扩展的物理操作符需要首先获取所有的元组后才能进行操作(例如聚集函数操作.没有索引辅助的排序等),这时要用物化节点将元组缓存起来.下 ...

  6. 跟我一起读postgresql源码(十三)——Executor(查询执行模块之——Join节点(上))

    Join节点 JOIN节点有以下三种: T_NestLoopState, T_MergeJoinState, T_HashJoinState, 连接类型节点对应于关系代数中的连接操作,PostgreS ...

  7. 跟我一起读postgresql源码(一)——psql命令

    进公司以来做的都是postgresql相关的东西,每次都是测试.修改边边角角的东西,这样感觉只能留在表面,不能深入了解这个开源数据库的精髓,遂想着看看postgresql的源码,以加深对数据库的理解, ...

  8. 跟我一起读postgresql源码(五)——Planer(查询规划模块)(下)

    上一篇我们介绍了查询规划模块的总体流程和预处理部分的源码.查询规划模块再执行完预处理之后,可以进入正式的查询规划处理流程了. 查询规划的主要工作由grouping_planner函数完成.在具体实现的 ...

  9. 跟我一起读postgresql源码(三)——Rewrite(查询重写模块)

    上一篇博文我们阅读了postgresql中查询分析模块的源码.查询分析模块对前台送来的命令进行词法分析.语法分析和语义分析后获得对应的查询树(Query).在获得查询树之后,程序开始对查询树进行查询重 ...

随机推荐

  1. 2017年PHP培训机构排名

    2017年PHP培训机构排名 PHP培训属于IT培训的一个领域.随着互联网的火爆,PHP也变得异常火爆.通过对PHP培训机构的调查与了解,到底学员选择哪一家的PHP培训机构才能够学到真正的技术,PHP ...

  2. find命令之xargs,exec

    一,find命令之xargs: 在 使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行.但有些系统对能够传递给exec的命 令长度有限制,这样 ...

  3. 【2017-05-30】WebForm文件上传

    用 FileUpload控件进行上传文件. <asp:FileUpload ID="FileUpload1"  runat="server" /> ...

  4. 由Find All References引发的思考。,

    今天在研究C#代码问题的时候遇到了一个Visual Studio的小问题.在Visual Studio 2013中,使用Find All References功能不能找到同一类型不同版本的所有引用,具 ...

  5. 码工具通过ICP备案

    5月22日,为广大程序员造福的在线工具--码工具 通过了ICP备案,这也意味着本站也越来越正规化,规范化.大家从今日起就可以在网站底部看到本站的ICP备案号. 备案/许可证编号:粤ICP备170597 ...

  6. Android6.0-运行时权限处理

    为什么需要有运行时权限? 大家都知道在Android6.0之前,权限在应用安装过程中只询问一次,以列表的形式展现给用户,如果点击取消(即不认可应用所申请的权限),则会取消应用的安装.而用户出于安装应用 ...

  7. mysql too many connections 问题

    我的处理步骤: 第一步:首次确定你的服务可不可以重启,如果可以重启转第二步,如果不可以重启转第三步,这个主要考虑已经部署到客户现场或者正在使用中的数据库不能重启. 第二步:查找mysql的安装路径,这 ...

  8. 模拟对象测试——EasyMock

    一.EasyMock 使用动态代理实现模拟对象创建,一般可以满足以下测试需求 1.要测试的模块依赖于其它自己控制不了的模块,如第三方服务,其它组员在开发的服务等,它们都没办法配合你来测试: 2.涉及到 ...

  9. MySql数据库基础操作——数据库、用户的创建,表的制作、修改等

    MySql 是一款使用便捷.轻量级的数据库.因为他体积小.速度快.安装使用简单.开源等优点,目前是使用最广泛的数据库.目前位于Oracle甲骨文公司旗下.那今天我们就来介绍一下数据库的基本操作.具体介 ...

  10. js数组及数组应用(冒泡和二分,遍历输出)

    一.定义:1)var arr=new Array(); 加数据:arr[0]=1; 2)定义同时赋值:var arr=new Array(1,2,3,4,5); 3)调用:var arr=new Ar ...