《数据库系统概论》第九章笔记
第九章 关系查询处理和查询优化
本章主要介绍关系数据库的查询处理和查询优化技术。
本章中重点为一些查询优化的思想。
查询优化一般可分为:
- 代数优化(逻辑优化)
- 物理优化(非代数优化)
9.1 关系数据库系统的查询处理
定义:
- 查询处理是关系数据库管理系统执行查询语句的过程。
任务:
- 其任务是把用户提交给关系数据库的查询语句转换为高效的查询执行计划
9.1.1 查询处理步骤
查询处理分为4个阶段:
- 查询分析
- 查询检查
- 查询优化
- 查询执行
查询分析
定义:
- 对查询语句进行扫描、词法分析和语法分析。
目的:
- 判断是否有语法错误;
- 无错,进入下一步;
- 有错,则报告语法错误
查询检查
定义:
- 对合法的查询语句进行语义分析、符号名转化、安全性检查和完整性初步检查
目的:
- 根据数据字典中有关的模式定义,检查语句中的数据库对象是否存在和有效;
- 如果是对视图的操作,则要用视图消解的方法转化成对基本表的操作;
- 还要根据数据字典中用户权限和完整性约束的定义,对用户的存取权限进行检查;
- 无权限或违反完整性约束,将拒绝执行该查询
- 此时的完整性检查,是初步的、静态的检查
检查通过后:
- 将把SQL查询语句转换成内部表示,即等价的关系代数表达式。这个过程中把数据库对象的外部名称转换成内部表示。
- 关系数据库关系系统一般都用查询树,也称为语法分析树来表示扩展的关系代数表达式
查询优化
定义:
- 每个查询都会有许多可供选择的执行策略和操作算法,查询优化就是选择一个高效执行的查询处理策略。
按照优化的曾以一般可将查询优化分为:
- 代数优化
- 指关系代数表达式的优化,即按照一定的规则,对关系代数表达式进行等价变换,改变代数表达式中操作的次序和组合,使查询执行更高效
- 物理优化
- 指存取路径和底层操作算法的选择。选择的依据一般有三种:
- 基于规则
- 基于代价
- 基于语义
- 指存取路径和底层操作算法的选择。选择的依据一般有三种:
查询执行
依据优化器得到的执行策略生成查询执行计划,由代码生成器生成执行这个查询计划的代码,然后加以执行,回送查询结果。
9.1.2 实现查询操作的算法
就不根据例子讲了,想具体可以直接看书,这里就记录下几种算法思想
选择操作的实现算法:
- 简单的全表扫描算法(table scan)
- 索引扫描算法(index scan)
选择操作时查询处理中最常用也是最耗时的操作之一。
连接操作的实现算法:
- 嵌套循环算法(nested loop join)
- 排序-合并算法(sort-merge join 或者 merge join)
- 索引连接算法(index join)
- hash join算法
简单的全表扫描算法
算法思想:
- 按照物理次序读表的M块到内存
- 检查内存中的每一个元组t,如果t满足选择条件,则输出t
- 如果表中还有未处理的块,重复前两步
优点:
- 只需要很小的内存就可以运行(最少1块)
- 对于规模较小的表,这种算法简单有效
缺点:
- 当表的规模很大,并且选择率(即满足条件的元组占全表比例)很低时,这种算法的效率较低。
索引扫描算法
如果选择条件上有索引(例如B+树索引或hash索引),可以用索引扫描方法。
算法思想:
- 先通过缩影找到满足条件的元组指针
- 再通过元组指针在基本表中找到元组
与全表扫描算法比较:
- 一把情况下,当选择率较低时,基于索引的选择算法要优于全表扫描算法。
- 但再某些情况,例如选择率较高,或者要查找的元组均匀分布再查找的表中,这时基于索引的选择算法性能不如全表扫描算法
- 这是因为除了对表的扫描操作,还要加上对B+树索引的扫描操作,对每一个检索码,从B+树根节点到叶子节点都要执行一次I/O操作。
特殊情况说明:
选择条件Sdept = 'cs' AND Sage > 20
,如果Sdept
和Sage
上都有索引。
则有两种算法可选:
- 一种是通过索引分别找到两个条件的元组指针集合,再求两个集合的交集,再到基本表中去检索。
- 另一中算法是通过
Sdept = 'cs'
找到一个元组指针集合,然后遍历这个指针集合,到基本表中将满足另一条件(Sage > 20
)的元组选择输出。
嵌套循环算法
这是最简通用的算法,可以处理包括非等值连接在内的各种连接操作。
算法思想:(连接条件:left.a = right.b)
- 对外层循环(左表)的每一个元组,检索内层循环(右表)中的每一个元组,并检查来这两个元组再连接属性上是否相等。
- 如果满足连接田间,则串接后作为结果输出,直到外层循环表中的元组处理完。
说白了就是两个for
循环,时间复杂度O(N*M)
注意:
再实际实现中,数据存取时按数据块读入内存中的,而不是按照元组进行I/O。
排序-合并算法
这是等值连接常用的算法,尤其适合参与连接的诸表已经排好序的情况。
算法思想:(连接条件:left.a = right.b)
- 如果参与连接的表没有排好序,首先将两个表分别按照连接属性排好序
- 取
left
表的第一个a
,依次扫描right
表的具有b=a
的元组,把它们连接起来 - 当扫描到
right
表b!= a
d的第一个元组时,返回left
表扫描它的下一个元组
说白了,就时先排序,然后两个for
循环,记录内存循环的index
,同时内存循环加break
条件。排序时间复杂度O(N*log(N)),循环时间复杂度O(N+M)。
优点:
- 虽然时间主要花在排序上,但是相比嵌套循环算法,在表规模较大时,还是能减少很多时间
索引连接
算法思想:(连接条件:left.a = right.b)
- 要求在
right
表上已经建立了属性b
的索引 - 对于
left
中的每一个元组,由a
的值,通过b
的索引找到相应的right
表元组 - 将左右表的元组连接起来
- 循环执行第二、三步,直到左表元组全部处理完
算法时间复杂度:
- 根据索引查找依次的时间复杂度O(layers),layers表示层数,一次查找需要比较layers-1次
- 所以总的算法复杂度是,O(N*layers)
hash join算法
hash join 算法也是处理等值连接的算法。
算法思想:
- 划分阶段:创建 hash 表,对包含较少元组的表,进行一遍处理,把它的元组通过连接属性按 hash 函数,分散到 hash 表的桶中;
- 连接阶段:对另一个表,同样进行一遍散列处理,找到合适的 hash 桶,将两个的匹配的元组连接起来。(感觉书上没表述清,应该是当发现 hash 表中已有key值时,将两个value对应的元组连接)
其实也是通过hash来快速匹配,从而减少循环次数。
算法时间复杂度O(N+M)。
缺点:
- 需要一个前提条件,数据量较小的表,在第一阶段后,能完全放入内存中的 hash 桶中。
- 有克服了这一缺点的改进算法,书中列出了英文参考文献,有需要深入研究的可以找找
9.2 关系数据库系统的查询优化
关系查询优化是影响关系数据库管理系统性能的关键因素。
9.2.1 查询优化概述
查询优化的优点;
- 不仅在于用户不必考虑如何最好地表达查询以获得较高的效率;
- 而且在于系统可以比用户程序的“优化”做得更好。
这是因为:
- 优化器可以从数据字典中获取许多统计信息(例如,每个关系表的元组数、每个属性值的分布情况、哪些属性上有索引等),从而根据这些信息做出正确的估计,选择高效的执行计划。(信息全面)
- 如果数据库的物理统计信息改变了,系统可以自动对查询进行重新优化以选择相适应的执行计划。(自适应)
- 优化器可以考虑数百种不同的执行计划,而程序员一般只能考虑有限的几种可能性。(考虑周全)
- 优化器中包含了很多复杂的优化技术,这些优化技术往往只有最好的程序员才能掌握。系统自动优化相当于所有热都拥有了这些技术。(掌握高级技术)
执行计划的选取:
- 目前关系数据库系统 通过某种代价模型计算出各种查询执行策略的执行代价,然后选取代价最小的执行方案。
在集中式数据库中,查询执行开销主要包括:
- 磁盘存取块数(I/O代价)
- 由于磁盘I/O,涉及机械动作,需要的时间相比内存操作要高几个数量级。一般将读写的块数,作为衡量查询代价的单位。
- 处理机时间(CPU代价)
- 查询的内存代价
分布式数据库中,则还要加上:
- 通信代价
9.2.2查询优化的一个实例
书上这节专门介绍了一个例子,对比使用三种查询方式需要读写的块数,来说明查询优化的重要性。这里简单说下,就不想详细计算了。
select Student.Sname from Student,SC where Student.Sno = SC.Sno and SC.Cno = '2'
第一种方式:
- 计算两个表的广义笛卡儿积
- 这一步产生的结果集太大,需要写入中间文件,写入块数大
- 从广义笛卡儿积中根据两个选择条件做选择操作
- 读取上一步中间文件,读取块数大
- 作投影操作,选出需要的列
第二中方式:
- 根据
Student.Sno = SC.Sno
计算两个表的自然连接- 由于已经用条件筛选了,中间文件减小很多,写入块数较小
- 从自然连接结果集中,根据
SC.Sno = '2'
做选择操作- 同样读取中间文件,读取块数较小
- 投影输出
第三种方式:
- 根据
SC.Sno == '2'
对SC
表做选择操作- 对单个表用等值条件筛选了,一般来讲,结果集较小,不需要中间文件,直接存在内存中
- 将第一步的结果集,与
Student
表根据Student.Sno = SC.Sno
做自然连接- 不需要读中间文件
- 投影输出
总结一下这个例子的用意:
- 说明了查询优化的必要性
- 给出了查询优化方法的初步概念:有选择和连接操作时,应先执行选择操作,使下一步要操作的集合趋于更小
- 上面三种方式,其实对应三个三个等价的代数表达式,显然前两个可以优化为第三个,从而引出下一节代数优化。
9.3 代数优化
代数优化策略是通过关系代数表达式的等价变换来提高查询效率的。
9.3.1 关系代数表达式等价变换规则
这一节列出了,11条等价变换规则,这里就不介绍了,有个概念就行。无非就是等价变换使整个执行代价越小越好。
9.3.2 查询树的启发优化
查询树就是执行的步骤。
典型的启发式规则有:(启发式规则指大多数情况下都适用,但是不是在每种情况下都是最好的规则)
- 选择运算应尽可能先做。
- 能使计算的中间结果大大变小
- 把投影运算和选择运算同时进行。
- 把同一关系的投影运算和选择运算同时进行,避免重复扫描关系
- 把投影桶其前或后的双目运算结合起来
- 与第二条的目的类似
- 把某些选择同在它前面要执行的笛卡儿积结合起来称为一个连接运算
- 同样
- 找出公共子表达式。
- 把公共子表达式结果存为中间结果,从外存读取中间结果的时间一般会比重新计算一次少很多。
9.4 物理优化
对于一个查询语句有许多存取方案,它们的执行效率不同,有的会相差很大;物理优化就是要选择高效合理的操作算法或存取路径,求得优化的查询计划,达到查询优化的目标。
物理优化方法有:
- 基于规则的启发式优化。
- 基于代价估算的优化。
- 两者结合的优化方法。
- 基于规则选取几种较优的候选方案,计算代价选出最优的方案。而不用计算所有的方案,较少代价估算的工作量。
9.4.1 基于规则的启发式优化
选择操作的启发式规则:
- 对于小关系,适用全表扫描,即使选择列上有索引。
- 对于大关系,启发式规则有:
- 对于选择条件是“主码=值”的查询,查询结果最多是一个元组,可以选择主码索引。
- 对于选择田间是“非主码=值”的查询,并且选择列上有索引,则要估算查询结果的元组数目,如果比例较小(<10%)可以使用索引扫描方法,否则还是适用全表顺序扫描。
- 索引记录的使每个元组的指针,重复率较高时,需多次根据指针去读取相应元组
- 而全表扫描,可以直接按存储顺序读取多个块(一个块包含多个元组)到内存中,所以重复率较高时,可能全表扫描更快。
- 对于选择条件是属性上的非等值查询或者范围查询,并且选择列上有索引,处理方法与上一条相同
- 对于用
AND
连接的合取条件- 如果有涉及这些属性的组合索引,则优先采用组合索引扫描方法
- 如果某些属性上有一般索引,可参考上面9.1.2节中,索引扫描算法中的两种
- 对于用OR连接的析取选择条件,一般使用全表扫描。
连接操作的启发式规则:
- 如果2个表都已经按照连接属性排序,则选用排序-合并算法。
- 如果一个表在连接属性上有索引,则可以选用索引连接算法。
- 如果上面2个规则都不适用,其中一个表较小。则可以选用hash join 算法
- 如果可以选用嵌套循环算法,则应该选择较小地表作为外循环的表
9.4.2 基于代价估算的优化
与启发式规则优化比较:
- 启发式规则优化:
- 是定性的选择,比较粗糙;
- 但是实现简单,而且优化本上的代价较小
- 适合解释执行的系统。因为解释执行系统,其优化开销包含在查询总开销中
- 代价估算优化
- 是定量的选择,比较精细
- 实现较复杂,需要根据统计信息做多次计算
- 适合编译执行的系统,一次编译,多次执行,查询优化和查询执行是分开的。
9.5 查询计划的执行
查询优化完成之后,关系数据库管理系统为用户查询生成了一个查询计划。
查询计划的执行可分为两种:
- 自顶向下的执行方式
- 是一种被动的、需求驱动的执行方式
- 自底向上的执行方式
- 是一种主动的执行方式