关系数据库查询优化分析

 

 

0 引言

从大多数的实例中来看,通常SQL语句消耗了70%~90%的数据库资源,而查询语句所基于的SELECT语句在SQL语句中又是代价最大的语句。因此人们往往通过对查询语句进行优化来提高整个数据库的性能。举例来说,如果一个数据库表信息积累到上百万甚至是上千万条记录,全表扫描一次需要数十分钟,甚至数小时;但如果采用比全表扫描更好的查询策略,往往可以使查询时间降为几分钟,由此可见查询优化技术的重要性。

 

1 查询语句执行过程

在数据库管理系统中,查询语句的执行过程如图1所示:

                                      1

l  语法分析与翻译(Query Parser):检查查询语句的合法性并将语句翻译成内部格式,通常是关系代数表达式或是与之等价的形式

l  查询优化器(Query Optimizer):改写关系表达式为所有可能的形式,并根据统计信息,评估出花费最少的访问计划(Access Plan)并生成执行代码。

l  执行引擎(Query Process):根据代码执行查询,并返回查询结果。

通常在系统编译的时候执行代码就已经生成并被保存在数据库中,只要访问计划没有更改,就无需重新编译。当程序执行时,引擎自动从数据库中调用代码,执行并返回查询结果。

    很显然在整个过程中,查询优化器是核心,而且实际上优化器还可以再细分。当然在实际的系统中,我们并不一定能够像图2这样把各个功能模块分得如此清晰。在图中,整个查询优化的过程被完整地分成2个阶段:一是改写阶段,另一个是计划编制阶段。

                                          2

在第一个阶段里只有一个改写模块,其他的模块都位于第二个阶段。计划编制是阶段二的中心模块。改写模块的主要作用是将由语法分析与翻译后得到的关系表达式转化成为所有可能的等价形式。计划编制模块则是综合其他模块的信息,选择最优的访问计划(Access Plan),并生成代码。事实上,图中的每个模块都和优化器的优化策略对应。

 

2         查询优化器的内部优化策略

优化策略多种多样,有基于规则的、基于代价的、基于语义的多种优化方法。

2.1 基于语义的查询优化

该优化主要思想是利用关系上定义的各种完整性约束条件,通过修改,把给定的查询变换为更有效的等价查询。语义优化把数据库完整性和查询处理这两个不同领域结合在一起,用产生新的查询来更好的发挥优化的作用。

2.2 基于代数规则的查询优化

利用关系代数表达式的等价规则,把初始查询树转换成最终查询树,以完成查询树的优化。主要的代数规则有:首先执行那些能够减小中间结果的操作,包括为了减少元组的数目尽可能早地执行选择操作,和为了减少属性的数目尽可能早地执行投影操作。通过在查询树中尽量用选择和投影操作可以实现这一点。另外,由于选择和投影操作生成的关系具有最少元组数和最短绝对长度,所以它们是最严格的操作,因此应该在执行其他操作之前先执行这两种操作。规则优化一般通过改变叶结点的顺序以避免笛卡儿积操作,并适当调整查询树中的剩余部分来实现的。然而,因为它不使用任何代价函数或统计信息,缺乏灵活性和适应性,在运用上有局限性。

2.3 基于代价和统计的查询优化

查询优化策略大体分为两类:一类是代数优化,即指把描述性语言经过某些等价变换后,变成某种执行效率较高的形式;另一类称为非代数优化,即指充分利用关系的存储结构及其特征,在优化措施中引人索引、排序及聚簇等具体存取方法来提高查询效率。一个好的查询优化策略应该是结合了代数优化和非代数优化的。查询优化器不能只依赖于基于代数规则,它还应该能够估计和比较使用不同策略执行查询的代价,并选出具有最低估计代价的策略。这种方法就是基于代价的优化。所谓基于代价的优化就是结合了代数优化和非代数优化,在所有解决方案的空间中搜索使目标查询代价最小化的方案。

对查询执行代价的评估的关键因素有:CPU占用、I/O 通道开销、通信线路占用。

基于代价的查询优化大大提高了系统查询的效率,但基于代价优化的一个缺点就是优化本身的代价。因此,许多系统仍然结合使用基于规则的方法来减少基于代价的方式中可选择的方案数。然而,即使是结合使用代数规则,基于代价的查询优化仍然有着无法解决的问题:无法产生全部可能的可选执行计划、代价估计的不准确等。于是基于统计的优化方法出现了。其实质就是在收集、分析统计信息的基础上,仍然采用代价模型,更为准确的估计执行计划的代价大小。 

统计信息应该至少包括:关系R中的元组数、关系R的一个记录)按字节计算的大小和关系R中某个属性上出现的不同的个数。有了这些统计信息,就可以估计各种运算的结果大小以及执行代价了。然而,为了保持基本统计信息的准确性,就需要不断的修改数据字典中的相关内容,这在数据库操作频繁的情况下,工作量将非常庞大。从而使统计信息的实时性、准确性成为制约代价优化的关键。

 

3         访问计划(Access Plan

查询优化器的作用是生成访问计划。它与访问方法(Access Method)、连接顺序、连接方法有关。其中连接顺序通常为左连接,所以一般只需考虑2个要素。

3.1 访问方法

3.1.1全表扫描(Full Table Scans, FTS

     读取表中所有的行,并检查每一行是否满足语句的WHERE限制条件。在全表扫描的情况下才能使用多块读操作可以使一次I/O能读取多块数据块,极大的减少了I/O总次数,提高了系统的吞吐量。在较大的表上不建议使用全表扫描,除非取出数据的比较多,超过总量的5% ~10%,或使用并行查询。在这种访问模式下,每个数据块只被读一次。

3.1.2 通过ROWID的表存取(Table Access by ROWID

行的ROWID指出了该行所在的数据文件、数据块以及行在该块中的位置,所以通过ROWID来访问数据可以快速定位到目标数据上,是访问单行数据的最快方法。 这种访问方法不会用到多块读操作,一次I/O只能读取一个数据块。

3.1.3索引扫描(Index Scanindex lookup

先通过index查找到数据对应的rowid(对于非唯一索引可能返回多个rowid),然后根据rowid直接从表中得到具体的数据。一个rowid唯一的表示一行数据,该行对应的数据块是通过一次I/O得到的,在此情况下该次I/O只会读取一个数据库块。 在索引中,除了存储每个索引的值外,索引还存储具有此值的行对应的ROWID值。索引扫描可以由2步组成:(1) 扫描索引得到对应的rowid值。(2) 通过找到的rowid从表中读出具体的数据。每步都是单独的一次I/O,但是对于索引,由于经常使用,绝大多数都已经CACHE到内存中,所以第1步的I/O经常是逻辑I/O,即数据可以从内存中得到。但是对于第2步来说,如果表比较大,则其数据不可能全在内存中,所以其I/O很有可能是物理I/O,这是一个机械操作,相对逻辑I/O来说,是极其费时间的。所以如果多大表进行索引扫描,取出的数据如果大于总量的5% -- 10%,使用索引扫描会效率下降很多。

根据索引的类型与where限制条件的不同,有4种类型的索引扫描:

 (1) 索引唯一扫描(index unique scan)

通过唯一索引查找一个数值经常返回单个ROWID。如果存在UNIQUE PRIMARY KEY 约束(它保证了语句只存取单行)的话,DBMS经常实现唯一性扫描。

(2) 索引范围扫描(index range scan)

使用一个索引存取多行数据,在唯一索引上使用索引范围扫描的典型情况下是在谓词(where限制条件)中使用了范围操作符(><<>>=<=between)

 (3) 索引全扫描(index full scan)

与全表扫描对应,也有相应的全索引扫描。而且此时查询出的数据都必须从索引中可以直接得到。

(4) 索引快速扫描(index fast full scan)

扫描索引中的所有的数据块,index full scan很类似,但是一个显著的区别就是它不对查询出的数据进行排序,即数据不是以排序顺序被返回。在这种访问方法中,可以使用多块读功能,也可以使用并行读入,以便获得最大吞吐量与缩短执行时间。

3.2连接方法

Join是一种试图将两个表结合在一起的谓词,一次只能连接2个表,表连接也可以被称为表关联。连接顺序对于查询的效率有非常大的影响。通过首先存取特定的表,即将该表作为驱动表,这样可以先应用某些限制条件,从而得到一个较小的表,使连接的效率较高。一般是在将表读入内存时,应用where子句中对该表的限制条件。

目前为止,无论连接操作符如何,典型的连接类型共有3种:

l  排序 - - 合并连接(Sort Merge Join, SMJ)

内部连接过程:

1) 首先生成表1需要的数据,然后对这些数据按照连接操作关联列进行排序。

2) 随后生成表2需要的数据,然后对这些数据按照连接操作关联列进行排序。

3) 最后两边已排序的行被放在一起执行合并操作,即将2个表按照连接条件连接起来

l  嵌套循环(Nested Loops, NL)

这个连接方法有驱动表(外部表)的概念。其实,该连接过程就是一个2层嵌套循环,所以外层循环的次数越少越好,这也就是我们为什么将小表或返回较小表的表作为驱动表(用于外层循环)的理论依据。但是这个理论只是一般指导原则,因为遵循这个理论并不能总保证使语句产生的I/O次数最少。有时不遵守这个理论依据,反而会获得更好的效率。

l  哈希连接(Hash Join, HJ)

访问一张表(通常是较小的表),并在内存中建立一张基于连接键的哈希表与bitmap。然后它扫描连接中其他的表(通常是较大的表),并根据哈希表检测是否有匹配的记录。Bitmap被用来作为一种比较快的查找方法,来检查在hash table中是否有匹配的行。特别的,当hash table比较大而不能全部容纳在内存中时,这种查找方法更为有用。这种连接方法也有NL连接中所谓的驱动表的概念,被构建为hash tablebitmap的表为驱动表,当被构建的hash tablebitmap能被容纳在内存中时,这种连接方式的效率极高。通常用于没有有效索引时。

 

你可能感兴趣的:(Database)