第五章 查询编译
1. 概述
查询处理器是数据库管理系统中的一个部件集合,它允许用户使用SQL语言在较高层次上表达查询,其主要职责是将用户的各种命令转化成数据库上的操作序列并执行。
查询处理分查询编译和查询执行两个阶段。查询编译主要任务是根据用户的查询语句生成数据库中最优执行计划,在此过程中要考虑视图、规则以及表的连接路径等问题。
当接收到查询语句后,首先将其传递到查询分析模块,进行词法、语法和语义分析。若是简单的命令(例如建表、创建用户、备份等)则将其分配到功能性命令处理模块;对于复杂的命令(SELECT/INSERT/DELETE/UPDATE)则要为其构建查询树,然后交给查询重写模块。查询重写模块接收到查询树后,按照该查询所涉及的规则和视图对查询树进行重写,生成新的查询树。
查询优化的核心是生成路径和生成计划两个模块。由于在整个查询执行过程中,表连接操作的开销最大,因此查询优化要处理的问题焦点在于如何计算最优的表连接路径。
2. 查询分析
查询分析是查询编译的第一个模块,它包括词法分析、语法分析和语义分析三个部分。它将用户输入的SQL命令替换为查询树(Query结构)。其中词法分析和语法分析分别借助词法分析工具Lex和语法分析工具Yacc来完成各自工作。
用户输入的SQL命令作为字符串传递给查询分析器,对其进行词法和语法分析生成分析树,然后进行语义分析得到查询树。
2.1. Lex和Yacc简介
词法分析工具Lex
词法分析工具Yacc
2.2. 词法和语法分析
&esmp; &esmp; scan.l的主要目标是识别出PostgreSQL中使用的所有关键字等
2.3. 语义分析
语义分析阶段会检查命令中是否有不符合语义规定的成分。如,所使用的表、属性、过程函数等是否存在,聚集函数是否可以合法使用等。其主要作用在于检查该命令是否可以正确执行。
3. 查询重写
在完成语义分析步骤得到查询树之后,会立刻对查询树进行查询重写处理。查询重写的入口函数是pg_rewrite_query,它在pg_analyze之后被pg_analyze_and_rewrite调用,且pg_rewrite_query的参数就是pg_analyze的返回值–查询树。查询重写模块使用规则系统判断来进行查询树的重写,如果查询树中某个目标被定义了转换规则,则该转换规则会被用来重写查询树。
3.1. 规则系统
pg中的视图其实就是用规则来实现的。创建一个视图时,会按照视图的定义自动创建一个规则。在对视图进行查询时,则用相应的规则将对视图的查询改写成对基表的查询。
用规则重写查询和触发器的功效非常相似,都可以在某种命令和条件下被激活,并且可以执行原始查询之外的动作。但是,从本质上还是有区别的。触发器对涉及的每个元组都要执行一次,而规则是对整个查询树进行修改或生成额外的查询。因此,如果在一个语句中涉及多个元组,一个规则通常比触发器的效率高。同时,触发器从概念上要比规则简单,触发器实现的功能可以用规则实现。但是目前某些约束不能用规则实现,特别是外键。
3.2. 查询重写的处理操作
查询重写部分的处理操作主要包括定义规则、删除规则以及利用规则进行查询重写。
4. 查询规划
在数据库查询中,最耗时的是表连接,查询优化的核心思想是“尽量先做选择操作,后做连接操作”,因为先做选择操作可以减少后面进行表连接的数据量。pg中提升子链接和提升子查询的目的在于将连接操作尽量推迟,并且在此过程中将子查询的WHERE子句与父查询合并。由于WHERE子句中各种行为约束条件的执行代价不一样,尽量将各个行约束条件合并到同一个WHERE子句中,再重新确定其执行顺序,就可以降低选择操作的代价。
4.1. 总体处理流程
查询规划的最终目的是得到可被执行器执行的最优计划,整个过程可分为预处理、生成路径和生成计划三个阶段。预处理实际上是对查询树的进一步改造,这种改造可通过SQL语句体现。在此过程中,最重要的是提升子链接和提升子查询。在生成路径阶段,接收到改造后的查询树后,采用动态规划算法或遗传算法,生成最优连接路径和候选的路径链表。在生成计划阶段,用得到的最优路径,首先生成基本计划树,然后添加GROUP BY、HAVING和ORDER BY等子句所对应的计划节点形成完整计划树。
4.2. 预处理
4.3. 生成路径
4.4. 生成可优化的MIN/MAX聚集计划
4.5. 生成普通计划
如果optimize_minmax_aggregates返回的是空值,则需要继续生成普通计划。生成普通计划的入口函数是create_plan,该函数为最优路径创建计划,依据其路径节点类型的不同,分别调用不同函数生成相应的计划。
4.6. 生成完整计划
生成路径仅仅考虑基本查询语句信息,并没有保留诸如GROUP BY、ORDER BY等信息。在生成基本计划树后,则会依据查询树相关约束信息在前面生成的普通计划之上添加相应的计划节点生成完整计划。
4.7. 整理计划树
5. 代价估计
规划器的核心工作就是建立多条路径,然后从中找出最优的那一条。同一个查询请求有不同路径主要是因为:表的不同访问方式、表之间不同的连接方式、表之间不同的连接顺序等因素造成的。而评价路径优劣的依据是用系统表pg_statistic中的系统统计信息估计出的不同路径的代价 ( Cost ) 。
5.1. 代价估算公式
代价估算从I/O次数和CPU开销两方面考虑,估算公式为P+W*T。其中,P表示在执行时所要访问的页面数,反映了磁盘I/O次数;T表示在执行时所要访问的元组数,反映了CPU开销;W表示磁盘I/O代价和CPU开销间的权重因子。计算代价时只需要考虑访问的页面数和元组数。
5.2. 选择度
5.3. 单个表的扫描代价
5.4. 两个表的连接代价
6. 遗传算法
7. 小结
数据库管理系统中,对性能影响最大的是查询处理器。查询处理器由查询编译器和执行器两部分组成,而查询编译器又包括查询分析器、查询预处理器和查询优化器。规划器/优化器的任务是创建一个优化了的执行计划。它首先生成完成查询所有可能的路径。这样创建的所有路径都产生相同的查询结果,而优化器的任务就是计算每个路径的开销并且找出开销最小的那条路径。