PostgreSQL查询引擎源码技术探索--读书笔记

目录

第1章 PostgreSQL概述

1.1 概述

作为数据库内核中的重要一环,查询引擎在整个数据库管理系统中起到了“大脑”的作用。查询语句是否以最优的方式来执行等均与查询引擎有着密不可分的联系;不同的数据库对同一条查询语句的执行时间各不相同,有快有慢。究其原因,除了存储引擎之间的差别,查询引擎生成的查询计划和执行计划的优劣直接影响数据库在查询时的性能表现。不同的查询引擎产生的查询计划千差万别,表现在查询效率上也千差万别。

作为连接服务器层(ServerFramework)与存储引擎层(StorageEngine)的中间层,查询引擎将用户发送来的SQL语句按照scan.l和gram.y中预先定义的SQL词法(LexcialRules)及语法规则(GrammaticRules)生成查询引擎系统内部使用的查询语法树形式(AbstractSyntaxTree,AST),查询引擎会将该查询语法树进行预处理:将其转换为查询引擎可处理的形式——查询树Query。

在由语法树查询树的转换过程中,查询引擎会将查询语句中的某些部分进行转换。例如,“*”会被为被扩展为相对应关系表的所有列,并在后续转换的过程中,根据语法树所标示的类型进行分类处理,如SELECT类型语句、UPDATE类型语句、CREATE类型语句等。在查询引擎语法树到查询树转换后,PostgreSQL查询引擎会使用pg_rewrite中设定的转换规则进行所谓的基于规则的转换,例如,PostgreSQL查询引擎会将VIEW进行转换,为后续的优化提供可能。在完成了基于规则的优化后,PostgreSQL查询引擎进入到我们称之为逻辑优化的阶段。在该阶段中,PostgreSQL查询引擎将完成对公共表达式的优化,子链接的上提,对JOIN/IN/NOTIN的优化处理(进行SemiJoin、AntiSemiJoin处理等),LateralJoin的优化等优化操作。在执行上述优化操作中,我们将遵循一条“简单”法则:先做选择运行(Operation),后做投影运算(Operation)。经过此阶段的优化操作后,所得到的查询树为一棵遵循了先选择后投影规则的最优查询树,并以此为基础构建最优查询访问路径(CheapestAccessPath)。在完成了对查询树的优化处理并获得最优查询访问路径后,PostgreSQL查询引擎接下来要做另外一件非常重要的事情是查询计划的生成(PlansGenerating)。PostgreSQL查询引擎会依据最优查询访问路径,通过遍历该查询访问路径,来构建最优查询访问路径对应的查询计划(QueryPlansorPlans)。

综上所述,一个查询引擎应该包括:查询语句接收模块、词法解析模块、语法解析模块、查询树改写模块(规则优化模块)、查询优化模块(包括逻辑优化和物理优化两部分)、查询计划生成模块、元数据管理模块、访问控制模块等基本模块。当然不同的查询引擎在实现时,这些模块的划分可能不同,但是一个普通的查询查询都应含有上述模块

1.2 查询语句优化

当查询引擎接收到一条用户查询请求后,查询引擎会依据该查询语句的类型进行分类处理;但在处理查询语句之前,考虑到复杂查询语句求解最优访问路径时的代价,有些查询引擎会使用查询计划缓存机制(QueryPlansCaching或QueryPathsCaching):数据库管理系统提供原生的最优查询访问路径代价缓存机制或使用第三方的查询计划缓存解决方案。但在使用此缓存机制时需要注意:查询语句需满足一定条件,例如满足不含有易失函数(VolatileFunction),语句中涉及的基表定义发生变化后的正确处理等条件后,才能对其使用缓存机制,否则可能导致查询结果不正确。

PostgreSQL对于查询语句的分类处理,从类型上可以分为两大类:工具型语句查询语句

1.2.1 工具类语句

工具类语句中包含:事务(Transaction)类语句,例如,开始事务、提交事务、回滚事务、创建SavePoint等;游标(Cursor)类语句,例如,打开游标、遍历游标、关闭游标等;内联过程语句类语句(InlineProceduralLangauge);表空间(TableSpace)操作类型语句,例如,创建表空间、删除表空间、修改表空间参数等;Truncate类语句;注释类语句;数据库对象安全标签类语句(SecurityLabeltoaDatabaseObject);SQLCopy类语句;Prepare类型语句;权限或角色操作相关类语句;数据库操作类语句,例如,创建数据库、删除数据库等;索引维护类语句;Explain语句;Vacuum语句。

1.2.2 查询类语句处理

对于非工具类查询语句,即普通查询类语句,除了经历与工具类查询语句一样的语法分析过程和词法分析过程,还需完成:

  1. 将原始语法树转换为查询语法树;

  2. 以查询语法树为基础对其进行逻辑优化;

  3. 对查询语句进行物理优化;

  4. 查询计划创建等过程。

当查询语句中涉及的基表数量较小时,由于其对应的最优解(最优查询访问路径)搜索空间较小,PostgreSQL将采用动态规划算法(DynamicProgramming)来求解最优查询访问路径;但当查询中涉及的基表数量较多时(该阈值将在后续章节进行讨论),将导致最优解的搜索空间以指数倍(Exponential)增长。此时,传统的动态规划算法将无法满足求解要求。为解决由于基表数量的增加所带来最优解求解时间的极速增长,PostgreSQL查询引擎引入了基因遗传算法(GeneticAlgorithm)来加速最优解的求解。

1.3 创建查询计划

与查询语句在逻辑优化和物理优化阶段不同,查询计划创建阶段的模块的功能相对单一,无须较多的查询优化理论知识,只需依照最优查询访问路径所描述的步骤,分类创建其对应的查询计划节点(Plans),最后将所有查询计划节点合并形成最后的查询计划树。

在获得查询计划后,PostgreSQL将查询计划送入执行器(Executor)中,执行器依据查询计划执行给出的表扫描操作获取满足条件的元组后按照指定的格式进行输出。

1.4 小结

我们将上述的优化过程简短地描述为如下流程:

(1)由应用程序建立到PostgreSQL服务器的连接。应用服务器发送查询请求至PostgresSQL服务器并从PostgreSQL服务器接收查询结果。

(2)查询引擎将查询语句依据所定义的词法规则和语法规则构建原始查询语法树。

(3)查询分析阶段,查询引擎将原始语法树转换为查询树。

(4)查询改写阶段,查询引擎将查询树依据系统中预先定义的规则对查询树进行转换。

(5)优化器(Planner)接收改写后的查询树并依据该查询树完成查询逻辑优化。

(6)优化器(Planner)继续对已完成逻辑优化的查询树进行查询物理优化并求解最优查询访问路径。

(7)执行器(Executor)依据最优查询访问路径进行表扫描操作并将获取的数据按一定格式创建返回值,然后将结果返回应用程序。

第2章 基表数据结构

2.1 概述

PostgreSQL在接收到用户的SQL查询语句后,由查询引擎完成对该查询语句的词法分析和语法分析(此项工作由函数parse_analyze依据语法树上节点的类型分别完成对INSERT、DELETE、UPDATE、SELECT等子句的分析,backend/parse/analyze.c),并完成对原始语法树的改写(Rewrite)操作(主要使用pg_rewrite、pg_rules等系统表中提供的重写规则对原始的语法树进行重写操作;pg_rewrite_query函数完成此项工作,backend/tcop/postgres.c)。查询引擎在完成上述的操作后,PostgreSQL会以由原始Node类型语法树转换而得的Query类型查询树为基础对该查询树进行等价变化(即逻辑优化和物理优化过程),形成最优的查询树,而经过优化的查询树是我们构建查询计划和执行计划的基础。

2.2 数据结构

2.2.1 查询树Query

注意,区分查询树Query和查询过程QueryExecution的区别,这里仅描述查询树,不包含查询执行过程的数据结构。即对查询语句的建模。

Query描述了一条SQL查询语句在经过词法和语法解析阶段后得到的查询树(查询语法树),该结构中包含了一条SQL语句中的各个语法部分。例如,由SQL标准可以看出,一条SELECT型查询语句中包含目标列(TargetListColumns)、范围表(RangeTables)、条件语句(ConditionalStatements)、聚集函数语句(AggregationStatements)等。因此,我们要描述一条SQL语句中的各个语法部分,必然需要相应的域(Feilds)来保存其查询语句中的各个语法部分。从这个角度出发,那么在Query类型的数据结构中需要有用来描述上述各个语法部分的域:描述目标列的targetList域;描述范围表的rangeTbls域;描述条件语句的WHERE域;描述聚集语句的HAVING、GROUPBY等子句的域……

2.2.2 子链接SubLink

除了子查询,子链接也是数据库理论和实践中优化的重点,可以说任何一个数据库查询引擎都会将其作为一个最基本优化点,无法想象一个不对子链接进行优化的查询引擎的存在。那么什么是子链接呢?不同于子查询的可单独执行,子链接通常不可以以独立的形式存在且不可以作为独立的查询语句执行。通常,子链接都与一定形式的比较条件相关联。例如,对于查询语句SELECT * FROM foo WHERE foo.col in(SELECT col FROM bar),其中foo.colin(SELECT col FROM bar)就是一子链接语句,而SELECT col FROM bar则是一条子查询语句。在明确了子链接与子查询之间的区别后,如何描述一个子链接呢?一条子链接表达式包括三个要素:比较的表达式、类型、数据源。例如,对于上述子链接语句,foo.colin为比较表达式,in为子链接的类型,SELECT col FROM bar称之为比较的数据源。

在PostgreSQL中,描述子链接的数据结构是SubLink。SubLink描述了表达式中出现的子链接,EXISTS、ALL、ANY、ROWCOMPARE、EXPR、ARRAY、CTE为PostgreSQL给出的子链接类型。

2.2.3 子查询计划SubPlan

在后续的优化过程中,对于满足优化条件的子链接(需要满足的条件将在后续章节中进行详细讨论),查询引擎会将其转为Join连接,即所谓的Join化处理。但并非所有的子链接都可转为Join连接,不可Join化的子链接仍然以原型方式存在于查询树中。为了在查询计划构建过程中表示此类划构建过程中的子链接中的子查询对象,即SubLink中subselecct所描述的语法对象。我们将在后续的查询优化章节中详细讨论SubPlan具体的构建方式,这里只是让读者对其描述的对象有一个初步的概念而已。

SubPlan从名字上看可知该数据类型属于查询计划范畴。但事实上,其并非真正的查询计划。SubPlan只是从形式上描述了子查询的一种可执行状态。

2.2 小结

特别注意,PostgreSQL将语句分为两类语句:

(1)可优化语句(OptimizableStatements)。

(2)非优化语句(NonoptimizableStatements)。

通常,工具语句(UtilityStatements)属于非优化语句范畴,但并非所有的工具语句都属于非优化语句范畴。同样,也存在非工具语句均属可优化语句范畴的情况。例如,DECLARECURSOR。非优化语句一般不会经过analyze阶段(parse/analyze.c)。同样,也不会经历rewriting和planning阶段,而是直接交由ProcessUtility函数完成。

程序的本质:程序是我们思想表达的载体,就如同文学作品一样,其用来表达作者当时的思想状况。同样,程序代码也是我们表达思想的一个手段和载体。

第3章 查询分析

3.1 概述

查询语句作为用户所输入的一串字符,我们需要知道该查询语句中包含的关键字是否正确?该查询语句是否满足SQL标准语法规则?若满足上述条件后,还需要知道该查询语句表示什么样的语义。这一系列的问题都需要我们了解。

3.2 问题描述

3.3 词法分析和语法分析

Lex程序(词法分析)通常由三部分构成:定义段(Definitions)、规则段(RulesSection)以及用户定义子程序段(ProceduresSection)。

Yacc(语法分析)也由三部分构成:定义段、规则段以及用户定义子程序段。

3.4 抽象语法树AST

查询语法树(SyntaxTree):查询语法树从查询语法上描述了一条查询语句。一条查询语句在经过词法分析和语法分析后,我们将得到一棵(或多棵)原始语法树(RawAbstractSyntaxTree)。而该原始语法树是无法被后续的优化器使用的。因此,为了能够给优化器提供其可接受的查询语法树(QueryTree),我们需要将原始语法树转换为查询语法树。

同时,由于原始语法树中存在着类似SELECT*FROMclassWHEREclass.gno='gradeone'中“*”的语法符号,以及需要确定语句中的class.gno对象的基表class是否存在gno字段等约束条件。因此,我们希望在后续的优化过程中对完成对语句中语法对象的有效性验证。函数pg_analyze_and_rewrite完成了原始语法到查询语法树的转换以及依据规则的改写工作。

3.5 查询分析

3.5.1 概述

当一条查询语句经过Lex词法分析和Yacc语法分析后,将得到一个原始语法树(RawSyntaxTree,RawParseTree)。该原始语法树由Yacc根据gram.y中定义的SQL标准规则创建而来。函数pg_analyze_and_rewrite接受一棵原始语法树并通过对该原始语法树的分析和重写操作后,函数将该原始语法树转换为一棵(或者多棵)查询语法树,而该查询语法树是后续所有优化操作的基础。

由上述的讨论可知,原始语法树到查询树的转换过程又分为两个阶段:原始语法树的分析阶段原始查询语法树的重写操作

3.5.2 查询分析-parse_analyze

由于我们以一棵树作为输入参数,那么我们可以通过遍历该树并根据树中的每个节点的类型来执行相应的转换处理操作。例如,如果当前节点的类型是一个INSERT语句,那么就调用INSERT处理语句来处理;如果该节点是SELECT类型的节点,那么就使用SELECT处理函数来完成对该节点的处理操作,以此类推,直到我们处理完原始语法树上所有的节点。

3.6 查询重写

3.6.1 概述

完成对原始查询语法树的分析后,下面的一个重要步骤就是依据现有的规则系统,对该语法树进行相应的改写。该过程由pg_rewrite_query来完成。规则系统描述了当一指定事件发生时,系统所采取的操作行为。

3.6.2 查询重写-pg_rewrite_query

通常系统不会在Rewrite阶段对查询树做进一步的处理(在pg_rewrite表中保存的是一个字符串形式的节点,通过stringtonode和nodetostring两个函数,将元数据表中的字符串转为相应的node类型的数据,这样我们就可以使用该node对象来替换query树中相应的节点了,而这也是我们使用pg_rewrite的原因。当然,前提条件是我们使用的描述字符串是合法的,即其能够转为合法的node对象)。在完成了基于规则的转换后,原始语法树树处理完毕,完成了从用户输入的查询语句字符串到查询树的转换操作。

3.7 小结

由用户输入的查询语句字符串到查询树之间相差了pg_analyze和pg_rewrite之间的距离。将用户的查询字符串经过词法和语法解析后,我们得到一棵原始语法树。之所以称其原始,是因为其基本“等价”地描述了用户的查询语句字符串,并未对类似“*”之类的语法单位进行语义理解,同样也未对该查询语句进行相应的语义层级的有效性验证,例如,范围表是否有效等。当我们在pg_analyze中使用transformSelectStmt等相关函数对原始语法树进行处理并得到查询语法树后,我们就完全理解了查询语句字符串的所有语法单位在查询语句中所表示的语义信息。

现有的数据库系统中存在两类查询优化策略:基于查询代价的优化策略(CostbasedOptimization,CBO);基于规则的查询优化策略(RulesbasedOptimization,RBO)。

第4章 查询逻辑优化

单独学习

第5章 查询物理优化

单独学习

第6章 查询计划的生成

6.1 查询计划的产生

查询优化作为查询引擎中理论性最强的一部分,一直都是学术界关注的焦点,性能的不断提高一直是所有数据库研究和内核开发人员孜孜以求的目标。随着存储层技术的发展,其所支持功能的不断完善和增加,必将会影响查询优化的设计和实现。同样,各种新兴的存储介质的出现也会影响查询引擎的发展。再者,大数据时代,对查询引擎又提出了不同的要求,这也给数据库内核人员提出了新的挑战。前面分析了查询优化过程中使用到的各种技术,希望读者能够从中学习到业界大牛们对这些问题处理的方式和方法。

6.2 生成查询计划-create_plan/create_plan_recurse

创建计划、创建程序会依据不同类型的查询访问路径,由不同的创建函数完成对该种类型查询访问路径的查询计划的创建。例如,对于SeqScan类型查询访问路径,由create_scan_plan函数完成顺序扫描类型的查询计划的创建;由create_join_plan完成MergeJoin、NestLoopJoin以及HashJoin三种连接类型的查询访问路径的查询计划的创建。

6.3 查询计划的阅读

在PostgreSQL中提供了两种不同类型的查询计划查询命令:EXPLAIN和EXPLAINANALYZE。

6.4 小结

在确立最优查询访问路径后,接来下的工作相对“简单”了:依据最优查询访问路径描述的内容创建相应的查询计划。例如,当为顺序访问路径时,则构建顺序扫描查询计划;当为连接访问查询路径时,则创建连接查询计划。

6.5 思考

在查询计划的创建过程中,其依据的是一条最优查询访问路径并以递归的方式对该查询访问路径创建相应的查询计划,创建的过程为一步一步的方式。通常会存在这样的情况:一棵查询访问树由相互独立的两棵子树构成。在对该查询访问路径树构建查询计划的过程中,通过遍历该查询访问树的方式(由叶子节点到根节点的方式)构建查询计划树,若该独立的两棵子查询访问路径树采用线程方式,则由两个线程独立地完成相应子树查询计划的构建,最后合并构成一棵完整的查询计划树,这样查询计划树的构建效率将大大提升。

通常通过查询访问路径来构建查询计划,再通过执行引擎来解释查询计划并执行。此时的执行计划与查询引擎的绑定较为紧密,存储引擎提供访问接口,执行引擎将查询计划按存储引擎提供的接口访问存储引擎。这样造成的结果是,PostgreSQL产生的优秀的查询计划无法应用到MySQL等数据库系统中,如果想让MySQL支持PostgreSQL产生的查询计划或执行计划,则需要修改大量的源码。假如将查询引擎按照Java的方式,无论是PostgreSQL还是MySQL所产生的查询计划或者执行计划转成标准中间格式(类似于Java中的字节码),这样只要PostgreSQL和MySQL提供对该标准格式的访问接口,那么就可以将PostgreSQL产生的查询计划或执行计划对应的标准格式无缝地运行在PostgreSQL存储引擎或者MySQL存储引擎,或是现在流行的HDFS之上,所需的仅仅是一个中间代码解释器(IntermediateCodesInterpreter,ICI)而已。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值