跟我一起读postgresql源码(六)——Executor(查询执行模块之——查询执行策略)
时光荏苒,岁月如梭。楼主已经很久没有更新了。之前说好的一周一更的没有做到。实在是事出有因,没能静下心来好好看代码。当然这不能作为我不更新的理由,时间挤挤还是有的,拖了这么久,该再写点东西了,不然人就怠懒了。不过这回,我准备写的精简些,一方面我想偷点懒省点时间,二来毕竟写太长大家也不一定爱看。
之前我说过的查询分析,查询重写和查询规划都是相当于是对查询的"编译"。那么编译完了就应该按照既定的策略去执行它。本篇就来介绍查询执行模块的代码(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(查询执行模块之——查询执行策略)的更多相关文章
- 跟我一起读postgresql源码(八)——Executor(查询执行模块之——可优化语句的执行)
2.可优化语句的执行 可优化语句的共同特点是它们被查询编译器处理后都会生成査询计划树,这一类语句由执行器(Executor)处理.该模块对外提供了三个接口: ExecutorStart.Executo ...
- 跟我一起读postgresql源码(十)——Executor(查询执行模块之——Scan节点(下))
接前文跟我一起读postgresql源码(九)--Executor(查询执行模块之--Scan节点(上)) ,本篇把剩下的七个Scan节点结束掉. T_SubqueryScanState, T_Fun ...
- 跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上))
从前面介绍的可优化语句处理相关的背景知识.实现思想和执行流程,不难发现可优化语句执行的核心内容是对于各种计划节点的处理,由于使用了节点表示.递归调用.统一接口等设计,计划节点的功能相对独立.代码总体流 ...
- 跟我一起读postgresql源码(七)——Executor(查询执行模块之——数据定义语句的执行)
1.数据定义语句的执行 数据定义语句(也就是之前我提到的非可优化语句)是一类用于定义数据模式.函数等的功能性语句.不同于元组增删査改的操作,其处理方式是为每一种类型的描述语句调用相应的处理函数. 数据 ...
- 跟我一起读postgresql源码(十一)——Executor(查询执行模块之——Materialization节点(上))
物化节点 顾名思义,物化节点是一类可缓存元组的节点.在执行过程中,很多扩展的物理操作符需要首先获取所有的元组后才能进行操作(例如聚集函数操作.没有索引辅助的排序等),这时要用物化节点将元组缓存起来.下 ...
- 跟我一起读postgresql源码(十三)——Executor(查询执行模块之——Join节点(上))
Join节点 JOIN节点有以下三种: T_NestLoopState, T_MergeJoinState, T_HashJoinState, 连接类型节点对应于关系代数中的连接操作,PostgreS ...
- 跟我一起读postgresql源码(一)——psql命令
进公司以来做的都是postgresql相关的东西,每次都是测试.修改边边角角的东西,这样感觉只能留在表面,不能深入了解这个开源数据库的精髓,遂想着看看postgresql的源码,以加深对数据库的理解, ...
- 跟我一起读postgresql源码(五)——Planer(查询规划模块)(下)
上一篇我们介绍了查询规划模块的总体流程和预处理部分的源码.查询规划模块再执行完预处理之后,可以进入正式的查询规划处理流程了. 查询规划的主要工作由grouping_planner函数完成.在具体实现的 ...
- 跟我一起读postgresql源码(三)——Rewrite(查询重写模块)
上一篇博文我们阅读了postgresql中查询分析模块的源码.查询分析模块对前台送来的命令进行词法分析.语法分析和语义分析后获得对应的查询树(Query).在获得查询树之后,程序开始对查询树进行查询重 ...
随机推荐
- spring MVC 乱码问题
(转) spring的字符集过滤通过用于处理项目中的乱码问题,该过滤器位于org.springframework.web.filter包中,指向类CharacterEncodingFilter,Cha ...
- 在Azure China用自定义镜像创建Azure VM Scale Set
在Azure China用自定义镜像创建Azure VM Scale Set 在此感谢世纪互联的工程师Johnny Lee和Lan,你们给了我很大的帮助.因为Azure China的官网没有给出完整的 ...
- 结构化CSS设计思维
LESS.SASS等预处理器给CSS开发带来了语法的灵活和便利,其本身却没有给我们带来结构化设计思维.很少有人讨论CSS的架构设计,而很多框架本身,如Bootstrap确实有架构设计思维作为根基. 要 ...
- Java IO流--练习2
1)写一个Java程序,输入3个整数,并求出三个数的最大数和最小数 代码: package 第十二章IO流; import java.io.BufferedReader; import java.io ...
- characterEncodingFilter作用
package com.demo.test; import java.io.IOException; import javax.servlet.Filter; import javax.servlet ...
- Linux上的防病毒软件ClamAV
Clam AntiVirus(ClamAV)是免费而且开放源代码的防毒软件,软件与病毒码的更新皆由社群免费发布.目前ClamAV主要是使用在由Linux.FreeBSD等Unix-like系统架设的邮 ...
- 一篇文章让你搞懂 SSL 证书
关于结婚这件事 那天和同事讨论到底什么才算是真正的「结婚」?这种话题本来是极其不应该存在的.传统意义的领个证书,办个婚礼.吃吃喝喝,但随着社会各族人民身心发展进化,原本那些繁琐流程简直是反人类,貌似现 ...
- TortoiseGit使用SSH
Windows TortoiseGit使用SSH连接 1 找到TortoiseGit自带的Puttygen工具 2.1 如果未生成过SSHKey,选择Generate(生成的过程中记得移动鼠标) 2. ...
- PHP数字价格格式化,保留两位小数
number_format(($v['cash']/100),2); demo=>9,271.15
- django-xadmin ModelAdmin中定义object_list_template无效的问题
环境:https://github.com/y2kconnect/xadmin-for-python3.git python3.5.2 django1.9.12 object_list_templat ...