跟我一起读postgresql源码(四)——Planer(查询规划模块)(上)
时间一晃周末就过完了,时间过得太快,不由得让人倍加珍惜。时间真是不够用哈~
好的不废话,这次我们开始看查询规划模块的源码吧。
查询规划部分的在整个查询处理模块应该是在一个非常重要的地位上,这一步直接决定了查询的方式与路径,很大程度上影响了数据库查询的查询性能。因此这一块代码量也很大,我也会花较多的笔墨来分析这个模块的代码。在篇幅上,可能查询规划这一模块我会用2到3篇文章来细细的说明下。今天这一篇先总体概述下查询规划模块的全貌,在介绍该模块的一个重要的子模块(总共三个主要模块)就结束吧,剩下的交给第二篇吧。
1.查询规划模块总体概况
废话不多说,我先上图。下图大概的刻画了查询规划模块里主要的函数调用关系,当然啦,最下层只画到主处理函数,主处理函数内部的调用关系在介绍每个主处理函数的时候再细细的说吧。
我简单介绍下吧。exec_simple_query函数是负责查询处理的主函数,在进行查询规划之前,它已经调用了查询分析和查询重写模块。还记得上一篇博文的查询重写模块么?该模块返回的是重写后的查询树链表。这里exec_simple_query函数将重写后的查询树链表交给查询规划模块进一步处理。查询规划模块的入口函数是pg_plan_queries函数。
pg_plan_queries函数调用pg_plan_query函数对每一个查询进行处理并返回PlannedStmt(执行计划)结构体链表。这里要注意,查询规划模块只会对非UTILITY命令进行处理。
而在pg_plan_query函数里,实际是调用planner函数负责查询计划的生成。
planner函数调用standard_planner函数进入标准的查询规划处理流程。该函数接受查询树以及相关参数,返回PlannedStmt结构体,详细的结构如下:
typedef struct PlannedStmt
{
NodeTag type;
CmdType commandType; /* select|insert|update|delete */
uint32 queryId; /* query identifier (copied from Query) */
bool hasReturning; /* is it insert|update|delete RETURNING? */
bool hasModifyingCTE; /* has insert|update|delete in WITH? */
bool canSetTag; /* do I set the command result tag? */
bool transientPlan; /* redo plan when TransactionXmin changes? */
struct Plan *planTree; /* tree of Plan nodes */
List *rtable; /* list of RangeTblEntry nodes */
/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
List *resultRelations; /* integer list of RT indexes, or NIL */
Node *utilityStmt; /* non-null if this is DECLARE CURSOR */
List *subplans; /* Plan trees for SubPlan expressions */
Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */
List *rowMarks; /* a list of PlanRowMark's */
List *relationOids; /* OIDs of relations the plan depends on */
List *invalItems; /* other dependencies, as PlanInvalItems */
int nParamExec; /* number of PARAM_EXEC Params used */
bool hasRowSecurity; /* row security applied? */
} PlannedStmt;
该结构体里包含了后续的查询执行模块所需要的全部信息,包括计划树(Plan)、子计划树(SubPlan)和其他重要的参数信息。
在standard_planner函数里,主要通过subquery_planner函数和set_plan_references函数分别完成查询计划的处理优化和清理工作。
set_plan_references函数这里不多做介绍,这不是查询计划的重点,我们把重点放在subquery_planner函数上。该函数接收Query(查询树),返回一个Plan(计划树)。
如图所示,我们可以看出subquery_planner函数的处理主要分为三部分。
1.1 预处理
主要调用相关预处理函数,依据消除冗余条件、减少递归层次和简化路径等原则对查询树进行预处理。主要包括:
- 1.特殊情况的处理,如内联返回值函数、预处理行标记和扩展继承表等;
- 2.提升子链接和提升子查询;
- 3.预处理表达式;
- 4.预处理连接条件.
1.2查询规划处理
此处主要调用grouping_planner函数来处理(inheritance_planner函数在处理完继承关系后仍然调用的是grouping_planner函数来处理)。在执行过程中,该函数不再对查询树做变换处理,而是将查询中的信息进行规范化并传给query_planner函数。query_planner函数调用make_one_rel函数进入查询优化阶段,并将最优路径放入cheapest_path。然后grouping_planner函数再调用get_cheapest_fractional_path_for_pathkeys函数寻找符合需求的排序路径sorted_path,并最终确定最优路径best_path并生成基本计划树(调用create_plan函数)。
1.3 清理计划树
调用set_plan_references函数清理现场,做一些变量的调整工作,不对计划树做本质的改变。
那么,接下来的部分和后续文章(如果有的话)就从这三个部分展开了。
2.预处理
在进行正式的工作之前,常常需要做一些准备工作。在查询规划模块尤其是这样,因为它的预处理阶段做的事情还是蛮多的,因此值得单独拿出一节来讲,另外从篇幅上考虑,讲完预处理这篇就差不多了,因为之后的查询规划主过程的内容只会更多,应该要单独拿出(至少)一篇讲了。
查询规划模块的预处理工作主要是提升子链接和子查询,预处理表达式和having子句等。处理的对象主要是查询树中的范围表rtable和连接树jointree.
2.1提升子链接和子查询
首先我们先说明下子链接和子查询的区别:子查询是一条完整的查询语句,而子链接是一条表达式,但是表达式内部也可以包含查询语句。直白点说呢就是:子查询是放在FROM子句里的而子链接则出现在WHERE子句或者HAVING子句中。
在subquery_planner函数里,调用pull_up_sublinks函数处理WHERE子句和JOIN/ON子句中的ANY和EXISTS类型的子链接。
在用pull_up_sublinks函数内部,调用pull_up_sublinks_jointree_recurse函数递归地处理连接树jointree:
1.对于RangeTblRef类型,直接返回;
2.对于FromExpr类型,递归调用pull_up_sublinks_jointree_recurse函数处理每个节点并调用pull_up_sublinks_qual_recurse函数处理约束条件;
3.对于JoinExpr类型,递归调用pull_up_sublinks_jointree_recurse函数处理左右子树并调用pull_up_sublinks_qual_recurse函数处理约束条件.
之后,subquery_planner函数调用pull_up_subqueries函数来提升子查询。当子查询仅仅是一个简单的扫描或者连接时,就会把子查询或者子查询的一部分合并到父查询中以进行优化。一般会分一下三种情况处理:
1.在范围表中存在子查询。对于简单的子查询,直接调用pull_up_simple_subquery函数进行提升;而对于简单的UNION ALL子查询,调用pull_up_simple_union_all函数进行提升,其他的情况则不处理;
2.在FROM表达式中存在子查询。对于FROM列表中的每个节点都调用pull_up_subqueries递归处理;
3.连接表达式中的子查询。调用pull_up_subqueries函数递归地处理.
这些话说的很头疼,大家看了估计也头疼。我们举一个例子好了,假设我们有这样一个SQL语句:
SELECT D.dname
FROM dept D
WHERE D.deptno IN
(SELECT E.deptno FROM emp E WHERE E.sal = 100);
从字面上看,如果该语句中的子查询被独立地规划,也就是说对于表dept中的每一个元组deptno值,都要搜索一遍emp表。显然这样的做法很蠢,代价也非常大。但是如果我们把子查询提升并合并到父查询中,那么我们看看效果。
先做提升子链接:
SELECT D.dname
FROM dept D , (SELECT E.deptno FROM emp E WHERE E.sal = 100) AS Sub
WHERE D.deptno = Sub.deptno;
然后再做提升子查询:
SELECT D.dname
FROM dept D ,emp E
WHERE D.deptno = E.deptno and E.sal = 100;
可以看到,这样操作以后的SQL语句只要先做一下过滤(E.sal = 100),然后再把结果和dept表做一下连接即可,大大提高了查询效率。
2.2预处理表达式
表达式的预处理工作主要由函数preprocess_expression完成。该函数采用递归扫描的方式处理PlannerInfo结构体里面保存的目标属性、HAVING子句、OFFSET子句、LIMIT子句和连接树jointree。总体来说,做了以下几件事:
1.调用flatten_join_alias_vars函数,用基本关系变量取代连接别名变量;
2.调用函数eval_const_expression进行常量表达式的简化,也就是直接计算出常量表达式的值。例如:"3+1 <> 4" 这种会直接被替换成“FALSE”;
3.调用canonicalize_qual函数对表达式进行规范化,主要是将表达式转换为最佳析取范式或者合取范式(这个博主也不太懂啊,尴尬);
4.调用函数make_subplan将子链接转换为子计划.
对于函数make_plan,还是要细细的说一下,其执行步骤如下:
1)首先复制子链接SubLink中的查询树Query,如果查询树是一个EXISTS型的子计划,那么则调用simplify_EXISTS_query函数对QUery副本进行处理;
2)调用subquery_planner函数为子链接生成计划,同时设置好参数tuple_fraction来告诉底层规划器需要去多少个元组。对于EXISTS型的子查询,设置该值为1.0,取回一个元组即可;对于ALL和ANY型的子查询,从概率上说大约取到50%的元组就可以得到结果,因此设置参数值为0.5;对于其他的查询,统一设置为0;
3)调用build_subplan函数将上一步生成的计划转换为SubPlan或者InitPlan的形式,并将当前查询层次的参数列表传递给它的子计划;
4)对于生成的是SubPlan而且是一个简单的EXISTS类型的查询,调用convert_EXISTS_to_ANY函数尝试将其转换为一个ANY类型的查询并创建查询计划.
2.3处理HAVING子句
对于HAVING子句来说,除了进行前面所提到的预处理外,还需要处理其中的每个条件。如果HAVING子句中没有聚集函数的话,那么它完全可以退化到WHERE子句中去,否则的话他将被写到查询树的HavingQual字段里面。
具体来说的话:
step1:初始化一个空的HAVING子句链表newHaving;
step2:扫描HAVING子句链表中的每一条HAVING子句:
1.如果HAVING子句中存在聚集函数、易失函数或者子计划中的一种或多种,则添加至newHaving链表中;
2.如果不包含GROUP子句,则添加至WHERE子句中;
3.其他情况,将这条HAVING子句添加至WHERE子句同时也保存至newHaving中.
step3:用处理后的HAVING、子句链表newHaving替换原来的HAVING子句链表.
举个例子,例如有如下SQL语句:
SELECT a FROM b WHERE a.c = 10 HAVING a.d > 5;
在HAVING子句里没有聚集,那么可以直接将"a.d > 5" 提升到WHERE子句中,即:
SELECT a FROM b WHERE a.c = 10 AND a.d > 5;
3.结语
所以这一篇就又这样水完了?不甘心啊。感觉写的蛮糟的。这次大概就把查询规划模块的总体和预处理阶段简单介绍了下,感觉思路有点乱,有空再捋捋。后面一篇再细细的说说查询规划的主处理过程吧~感觉是块硬骨头,不好啃。
好的,大家下期见~
跟我一起读postgresql源码(四)——Planer(查询规划模块)(上)的更多相关文章
- 跟我一起读postgresql源码(五)——Planer(查询规划模块)(下)
上一篇我们介绍了查询规划模块的总体流程和预处理部分的源码.查询规划模块再执行完预处理之后,可以进入正式的查询规划处理流程了. 查询规划的主要工作由grouping_planner函数完成.在具体实现的 ...
- 跟我一起读postgresql源码(八)——Executor(查询执行模块之——可优化语句的执行)
2.可优化语句的执行 可优化语句的共同特点是它们被查询编译器处理后都会生成査询计划树,这一类语句由执行器(Executor)处理.该模块对外提供了三个接口: ExecutorStart.Executo ...
- 跟我一起读postgresql源码(十)——Executor(查询执行模块之——Scan节点(下))
接前文跟我一起读postgresql源码(九)--Executor(查询执行模块之--Scan节点(上)) ,本篇把剩下的七个Scan节点结束掉. T_SubqueryScanState, T_Fun ...
- 跟我一起读postgresql源码(三)——Rewrite(查询重写模块)
上一篇博文我们阅读了postgresql中查询分析模块的源码.查询分析模块对前台送来的命令进行词法分析.语法分析和语义分析后获得对应的查询树(Query).在获得查询树之后,程序开始对查询树进行查询重 ...
- 跟我一起读postgresql源码(六)——Executor(查询执行模块之——查询执行策略)
时光荏苒,岁月如梭.楼主已经很久没有更新了.之前说好的一周一更的没有做到.实在是事出有因,没能静下心来好好看代码.当然这不能作为我不更新的理由,时间挤挤还是有的,拖了这么久,该再写点东西了,不然人就怠 ...
- 跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上))
从前面介绍的可优化语句处理相关的背景知识.实现思想和执行流程,不难发现可优化语句执行的核心内容是对于各种计划节点的处理,由于使用了节点表示.递归调用.统一接口等设计,计划节点的功能相对独立.代码总体流 ...
- 跟我一起读postgresql源码(二)——Parser(查询分析模块)
上篇博客简要的介绍了下psql命令行客户端的前台代码.这一次,我们来看看后台的代码吧. 十分不好意思的是,上篇博客我们只说明了前台登陆的代码,没有介绍前台登陆过程中,后台是如何工作的.即:后台接到前台 ...
- 跟我一起读postgresql源码(十三)——Executor(查询执行模块之——Join节点(上))
Join节点 JOIN节点有以下三种: T_NestLoopState, T_MergeJoinState, T_HashJoinState, 连接类型节点对应于关系代数中的连接操作,PostgreS ...
- 跟我一起读postgresql源码(七)——Executor(查询执行模块之——数据定义语句的执行)
1.数据定义语句的执行 数据定义语句(也就是之前我提到的非可优化语句)是一类用于定义数据模式.函数等的功能性语句.不同于元组增删査改的操作,其处理方式是为每一种类型的描述语句调用相应的处理函数. 数据 ...
随机推荐
- leetcode539
public class Solution { public int FindMinDifference(IList<string> timePoints) { * ]; foreach ...
- 【289】◀▶ Python I/O & 读写文本文件
参考:Python 文件 I/O 参考:Python OS 文件/目录方法 目录: 01 open 函数 用于打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写. 02 F ...
- mongo 修改器 $inc/$set/$unset/$pop/$push/$pull/$addToSet
mongo $inc 可以对集合里面的某些值是数字的增减.看代码 $set 可以进行修改,并且不存在的时候默认添加. 同时还能该变数据的类型. 还可以该变内嵌元素的值 用.调用 $unset 删除 ...
- 69. Sqrt(x) 求根号再取整
[抄题]: Implement int sqrt(int x). Compute and return the square root of x, where x is guaranteed to b ...
- solr开发 小案例
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" ...
- PostgreSQL9.3+PostGIS2.1安装配置
Postgresql——Postgresql是一种对象关系型数据库.下载地址:http://www.postgresql.org/download/ postgis (可选)——是一个空间数据库,它扩 ...
- Part4_lesson1---Bootloader设计蓝图
1.bootloader的作用 2.u-boot是bootloader业界的老大 u-boot分为自主模式和开发模式 3.建立U-boot工程 uboot不能在window下面进行解压,因为在wind ...
- spark源码阅读之network(3)
TransportContext用来创建TransportServer和TransportclientFactory,同时使用TransportChannelHandler用来配置channel的pi ...
- 黑盒测试实践-小组任务分配和安排-Day01
实践作业2:黑盒测试实践 小组成员 组长:华同学 成员:覃同学.沈同学.刘同学.郭同学.穆同学 任务分解 任务1:分析系统需求 参考软件需求规格说明书模板描述系统需求.如果系统功能特别多,特别复杂,则 ...
- wireshark抓取qq数据包
抓包接口设置成本地连接 点击start,登录qq,输入oicq进行过滤qq包 找到第一个OICQ,点击后,点击oicq-IM software 可以看到自己登录的QQ号码为765343409 本机IP ...