关系查询处理和查询优化
查询优化分为两类:
- 代数优化:也称逻辑优化,是指关系代数表达式的优化。
- 物理优化:也称非代数优化,是指存取路径和底层操作算法的选择。
关系数据库系统的查询处理
查询处理步骤
关系数据库管理系统查询处理分为:查询分析、查询检查、查询优化、查询执行。
查询分析:对查询语句进行扫描、词法分析和语法分析。
- 词法分析:从查询语句中识别出正确的语言符号。
- 语法分析:进行语法检查。
查询检查
- 合法性检查:根据数据字典中有关的模式定义检查语句中的数据库对象,如关系名、属性名是否存在和有效。
- 视图转换:如果是对视图的操作,则要用视图消解方法把对视图的操作转换成对基本表的操作。
- 安全性和完整性初步检查:根据数据字典中的用户权限和完整性约束定义对用户的存取权限进行检查。
检查通过后把 SQL 查询语句转换成内部表示,即等价的关系代数表达式。关系数据库管理系统一般都用查询树,也成为语法分析树来标识扩展的关系代数表达式。
查询优化:查询优化即选择一个高效执行的查询处理策略。
查询优化分类
- 代数优化/逻辑优化:指关系代数表达式的优化,即按照一定的规则,通过对关系代数表达式进行等价变化,改变关系代数操作的次序和组合,是查询执行更高效。
- 物理优化:存取路径和底层操作算法的选择。
查询优化的选择依据
- 基于规则(rule based)
- 基于代价(cost based)
- 基于语义(semantic based)
查询执行:依据优化器得到的执行策略生成查询执行计划,由代码生成器(code generator)生成执行查询计划的代码,然后执行这个查询计划,会送查询结果。
实现查询操作的算法示例
选择操作的典型实现
(1)全盘扫描方法(Table Scan)
对查询的基本表顺序扫描,逐一检查每个元组是否满足选择条件,把满足条件的元组作为结果输出。该方法适合小表,不适合大表。
(2)索引扫描方法(Index Scan)
适合于选择条件中的属性上有索引(例如 B+树索引或 Hash 索引),通过索引先找到满足条件的元组主码或元组指针,再通过元组指针直接在查询的基本表中找到元组。
例:SELECT * FROM Student WHERE <条件表达式>;
考虑<条件表达式>的几种情况:
C1:无条件;
C2:Sno='2024001';
C3:Sage>20;
C4:Sdept='CS' AND Sage>20;
全表扫描算法思想
假设可以使用的内存为 M 块
- 按照物理次序读 Student 的 M 块到内存;
- 检查内存的每个元组 t,如果 t 满足选择条件,则输出 t;
- 如果 Student 还有其他块未被处理,重复 1 和 2。
索引扫描算法
【例】SELECT * FROM Student WHERE Sno='2024001';
假设 Sno 上有索引(或 Sno 是散列码)。
算法:使用索引(或散列)得到 Sno 为‘2024001’元组的指针,通过元组指针在 Student 表中检索到该学生。
【例】SELECT * FROM Student WHERE Sage>20;
假设 Sage 上有 B+树索引。
算法:使用 B+树索引找到 Sage = 20 的索引项,以此为入口点在 B+树的顺序集上得到 Sage > 20 的所有元组指针,通过这些元组指针到 Student 表中检索到所有年龄大于 20 的学生。
【例】SELECT * FROM Student WHERE Sdept='CS' AND Sage>20;
假设 Sdept 和 Sage 上都有索引。
算法一:分别用 Index Scan 找到 Sdept ='CS'的一组元组指针和 Sage > 20 的另一组元组指针,求这两组指针的交集,到 Student 表中检索得到计算机系年龄大于 20 的学生。
算法二:找到 Sdept ='CS'的一组元组指针,通过这些元组指针到 Student 表中检索,并对得到的元组检查另一些选择条件(如 Sage > 20)是否满足,把满足条件的元组作为结果输出。
连接操作的实现
连接操作是查询处理中最耗时的操作之一,这里讨论等值连接(或自然连接)最常用的实现算法。
【例】SELECT * FROM Student,SC WHERE Stident.Sno=SC.Sno;
(1)嵌套循环算法(nested loop join)
- 对外层循环(Student 表)的,每一个元组(s),检索内层循环(SC 表)中的每一个元组(sc);
- 检查这两个元组在连接属性(Sno)上是否相等。
- 如果满足连接条件,则串接后作为结果输出,直到外层循环表中的元组处理完为止。
(2)排序-合并算法(sort-merge join 或 merge join)
- 如果连接的表没有排好序,先对 Student 表和 SC 表按连接属性 Sno 排序。
- 取 Student 表中第一个 Sno,依次扫描 SC 表具有相同 Sno 的元组(如下图)。
- 当扫描到 Sno 不相同的第一个 SC 元组时,返回 Student 表扫描它的下一个元组,再扫描 SC 表中具有相同 Sno 的元组,把它们连接起来。
- 重复上述步骤直到 Student 表扫描完。
(3)索引连接(index join)算法
- 在 SC 表上已经建立了属性 Sno 的索引。
- 对 Student 中每一个元组,由 Sno 值通过 SC 的索引查找相应的 SC 元组。
- 把这些 SC 元组和 Student 元组连接起来。
- 循环执行 1、2,直到 Student 表中的元组处理完为止。
(4)Hash Join 算法
把连接属性作为 Hash 码,用同一个 Hash 函数把 Student 表和 SC 表中的元组散列到 Hash 表中。
第一阶段:划分阶段,也称为创建阶段
- 对包含较少元组的表(如 Student 表)进行一遍处理,把它的元组按 Hash 函数分散到 Hash 表的桶中。
第二阶段:试探阶段,也成为连接阶段
- 对包含较多元组的表(如 SC 表)进行一遍处理,把 SC 表的元组也按同一个 Hash 函数(Hash 码是连接属性)进行散列,把 SC 元组与桶中来自 Student 表并与之相匹配的元组连接起来。
算法前提:较小的表在第一阶段后完全放入内存 Hash 桶中。
关系数据系统的查询优化
查询优化在关系数据库系统中有着非常重要的地位。关系查询优化是影响关系数据库管理系统性能的关键因素。由于关系表达式的语义级别很高,使关系系统可以从关系表达式中分析查询语义,提供了执行查询优化的可能性。
查询优化概述
关系系统的查询优化:是关系数据库管理系统实现的关键技术,又是关系系统的优点所在,减轻了用户选择存取路径的负担。用户只要提出“干什么”,不必指出“怎么干”。
非关系系统:用户使用过程化的语言表达查询要求,执行何种记录级的操作,以及操作的序列是由用户来决定的。因此,用户必须了解存取路径,系统要提供用户选择存取路径的手段,查询效率由用户的存取策略决定,如果用户做了不当的选择,系统是无法对此加以改进的。
查询优化的优点不仅在于用户不必考虑如何最好地表达查询以获得较好的效率,还在于系统可比用户程序的“优化”做的更好。主要原因是:
- 优化器可以从数据字典中获取许多统计信息,而用户程序则难以获得这些信息。
- 如果数据库的物理统计信息改变了,系统可自动对查询重新优化以选择相适应的执行计划。在非关系系统中必须重写程序,而重写程序在实际应用中往往是不太可能的。
- 优化器可以考虑百种不同的执行计划,程序员一般只能考虑有限的几种可能性。
- 优化器中包括了很多复杂的优化技术,这些优化技术往往只有最好的程序员才能掌握。系统的自动优化相当于使得所有人都拥有这些优化技术。
关系数据库管理系统通过某种代价模型计算出各种查询执行策略的执行代价,然后选取代价最小的执行方案。
在集中式数据集中,执行代价主要包括:
- 磁盘存取块数(I/O 代价)、处理机时间(CPU 代价)、查询的内存开销,其中 I/O 代价是最主要的,因为 I/O 操作设计机械动作。
在分布式数据库中,执行代价主要包括:
- 总代价 = I/O 代价+CPU 代价+内存代价+通信代价
【说明】
- 因为 I/O 操作设计机械动作较耗时,所以在计算查询代价时一般用查询处理读写的块数作为衡量单位。
- 查询优化的总目标是选择有效的策略,求得给定关系表达式的值,使得查询代价最小(实际上是较小)。
一个实例
一个关系查询可以对应不同的执行方案,其效率可能相差非常大。
例:求选修了2号课程的学生姓名。
SELECT Student.Sname
FROM Student,SC
WHERE Student.Sno=SC.Sno AND SC.Cno='2';
假定学生-课程数据库中有 1000 个学生记录,10000 个选课记录,选修 2 号课程的选课记录为 50 个。
可以用多种等价的关系代数表达式来完成这一查询:
第一种情况
计算广义笛卡尔积
- 在内存中尽可能多地装入某个表(如 Student 表)的若干块,留出一块存放另一个表(如 SC 表)的元组。
- 把 SC 中的每个元组和 Student 中每个元组连接,连接后的元组装满一块后就写到中间文件上。
- 从 SC 中读入一块和内存中的 Student 元组连接,直到 SC 表处理完。
- 重复上述处理过程,直到把 Student 表处理完。
设一个块能装 10 个 Student 元组或 100 个 SC 元组,在内存中存放 5 块 Student 元组和 1 块 SC 元组,则读取总块数为:
块。 其中:读 Student 表 100 块,读 SC 表 20 遍,每遍 100 块,则总计要读取 2100 数据块。
连接后的元组数位
。设每块能装 10 个元组,则写出 块。 作选择操作
- 依次读入连接后的元组,按照选择条件选取满足要求的记录,假定内存处理时间忽略,读取中间文件花费的时间(同写中间文件一样)需读入
块。若满足条件的元组仅 50 个,均可放在内存。
- 依次读入连接后的元组,按照选择条件选取满足要求的记录,假定内存处理时间忽略,读取中间文件花费的时间(同写中间文件一样)需读入
作投影操作
- 把第(2)步的结果在 Sname 上作投影输出,得到最终结果。
- 第一种情况下执行查询的总读写数据块
。
第二种情况
计算自然连接
- 执行自然连接,读取 Student 和 SC 表的策略不变,总的读取数仍位 2100 块,自然连接的结果比第一种情况大大减少,为
个元组,写出数据块 块。
- 执行自然连接,读取 Student 和 SC 表的策略不变,总的读取数仍位 2100 块,自然连接的结果比第一种情况大大减少,为
读取中间文件块,执行选择运算,读取的数据块
块。 把第二步结果投影输出
第二种情况下执行查询的总读写数据块
第三种情况
- 先对 SC 表作选择运算,只需读一遍 SC 表,存取 100 块,因为满足条件的元组仅 50 个,不必使用中间文件。
- 读取 Student 表,把读入的 Student 元组和内存中的 SC 元组作连接。也只需读一遍 Student 表共 100 块。
- 把连接结果投影输出。
第三种情况总的读写数据块
【分析】
假如 SC 表的 C 弄字段上有索引,第一步就不必读取所有的 SC 元组而只需读取 Cno =‘2’的元组(50 个)。存取的索引块和 SC 中满足条件的数据块大约总共 3~4 块。
若 Student 表在 Sno 上也有索引,不必读所有的 Student 元组,因为满足条件的 SC 记录仅 50 个,涉及最多 50 个 Student 记录,读取 Student 表的块数也可大大减少。
【总结】
把代数表达式
有选择和连接操作时,先做选择操作,这样参加连接的元组就可以大大减少,这时代数优化。
在
对于 Student 和 SC 表的连接,利用 Student 表上的索引,采用索引连接代价也较小,这就是物理优化。
代数优化
关系代数表达式等价变换规则
代数优化策略是通过对关系代数表达式的等价变换来提高查询效率。
关系代数表达式的等价是指用相同的关系代替两个表达式中相应的关系,所得到的结果是相同的,两个关系表达式
常用的等价变换规则
连接、笛卡尔积的交换律
设
和 是关系代数表达式, 是连接运算的条件,则有
连接、笛卡尔积的结合律
设
是关系代数表达式, 和 是连接运算的条件,则有
投影的串接定律
是关系代数表达式, 是属性名,且 构成 的子集。
选择的串接定律
是关系代数表达式, 、 是选择条件,选择的串接律说明选择条件可以合并,这样一次就可检查全部条件。
选择与投影操作的交换律
选择条件
只涉及属性 ,若 中有不属于 的属性 ,则有更一般的规则:
选择与笛卡尔积的交换律
如果
中涉及的属性都是 中的属性,则: 如果
,且 只涉及 中的属性, 只涉及 中的属性,则由上面的等价变换规则 1,4,6 可推出: 若
只涉及 中的属性, 涉及 和 两者的属性,则仍有: - 它使部分选择在笛卡尔积前先做
选择与并的分配律
设
, , 有相同的属性名,则:
选择与差运算的分配律
若
, 有相同的属性名,则:
选择对自然连接的分配律
投影与笛卡尔积的分配律
设
和 是两个关系表达式, 是 的属性, 是 的属性,则:
投影与并的分配律
设
和 有相同的属性名,则:
查询树的启发式优化
典型的启发式规则:
- 选择运算应尽可能先做。 在优化策略中这是最重要、最基本的一条。
- 把投影运算和选择运算同时进行。 如有若干投影和选择运算,并且它们都对同一个关系操作,则可以在扫描完此关系的同时完成所有这些运算以避免重复扫描关系。
- 把投影同其前或其后的双目运算结合起来,没必要为了去掉某些字段而扫描一遍关系。
- 把某些选择同在它前面要执行的笛卡尔积结合起来成为一个连接运算,连接(特别是等值连接)运算要比同样关系上的笛卡尔积省很多时间。
- 找出公共子表达式。 如果这种重复出现的子表达式的结果不是很大的关系,并且从外存中读入这个关系比计算该子表达式的时间少得多,则先计算一次公共子表达式并把结果写入中间文件是合算的。当查询的是视图时,定义视图的表达式就是公共子表达式的情况。
遵循这些启发式规则,应用“关系代数表达式等价变换规则”的等价变换公式来优化关系表达式的算法。
算法:关系表达式的优化。
输入:一个关系表达式的查询树。
输出:优化的查询树。
方法:
利用等价变换规则 4 把形如
的表达式变换为 。 对每一个选择,利用等价变换规则 4~9 尽可能把它移到树的叶端。
对每一个投影利用等价变换规则 3,5,10,11 中的一般形式尽可能把它移向树的叶端。
- 【注意】等价变换规则 3 使一些投影消失或使一些投影出现,规则 5 把一个投影分裂为两个,其中一个有可能被移向树的叶端。
利用等价变换规则 3~5,把选择和投影的串接合并成单个选择、单个投影或一个选择后跟一个投影,使多个选择或投影能同时执行,或在一次扫描中全部完成。
把上述得到的语法树的内节点分组。
- 每一双目运算(
)和它所有的直接祖先为一组(这些直接祖先是 运算)。 - 如果其后代直到叶子全是单目运算,则也将它们并入该组,但当双目运算是笛卡尔积(
),而且后面不是与它组成等值连接的选择时,则不能把选择与这个双目运算组成同一组。
- 每一双目运算(
【例】下面给出例中 SQL 语句的代数优化示例。
例:求选修了2号课程的学生姓名。
SELECT Student.Sname
FROM Student,SC
WHERE Student.Sno=SC.Sno AND SC.Cno='2';
- 把 SQL 语句转换成查询树。
- 为了使用关系代数表达式的优化法,假设内部表示是关系代数语法树,则查询树如下图所示。
- 对查询树进行优化。利用规则 4、6 把
移到叶端,查询树便转换成下图优化的查询树,这就是 的查询树表示。
物理优化
代数优化改变查询语句中操作的次序和组合,不涉及底层的存取路径。对于查询语句有许多存取方案,它们的执行效率不同,仅仅进行代数优化是不够的。
物理优化就是要选择高校合理的操作算法或存取路径,求得优化得查询计划。
物理优化方法:
- 基于规则的启发式优化。启发式规则是指那些在大多数情况下都适用,但不是在每种情况下都是适用的规则。
- 基于代价估算的优化。优化器估算不同执行策略的代价,并选出具有最小代价的执行计划。
- 两者结合的优化方法。常常先使用启发式规则,选取若干较优的候选方案,减少代价估算的工作量。然后分别计算这些候选方法的执行代价,较快地选出最终的优化方案。
基于启发式规则的存取路径选择优化
选择操作的启发式规则
对于小关系,使用全表顺序扫描,即使选择列上有索引。
对于大关系,启发式规则有:
- 对于选择条件是“主码 = 值”的查询,查询结果最多是一个元组,可以选择主码索引。一般的关系数据库管理系统会自动建立主码索引。
- 对于选择条件是“非主属性 = 值”的查询,并且选择列上有索引,要估算查询结果的元组数目。如果比例较小(< 10%)可以使用索引扫描方法,否则还是使用全表顺序扫描。
- 对于选择条件是属性上的非等值查询或者范围查询,并且选择列上有索引,要估算查询结果的元组数目,如果比例较小(< 10%)可以使用索引扫描方法,否则还是使用全表顺序扫描。
- 对于用 AND 连接的合取选择条件,如果有涉及这些属性的组合索引,有限采用组合索引扫描方法。如果某些属性上有一般的索引,可以用索引扫描方法,其他情况:使用全表顺序扫描。
- 对于用 OR 连接的析取选择条件,一般使用全表顺序扫描。
连接操作的启发式规则
如果 2 个表都已按照连接属性排序,选用排序-合并算法。
如果一个表在连接属性上有索引,选用索引连接算法。
如果上面 2 个规则都不适用,其中一个表较小,则可以选用 Hash join 算法。
可以选用嵌套循环方法,并选择其中较小的表(占用的块数较少的表),作为外表(外循环的表)。理由是:
- 设连接表 R 与 S 分别占用的块数为 Br 与 Bs,连接操作使用的内存缓冲区块数为 K,分配 K-1 块给外表,如果 R 为外表,则嵌套循环法存取的块数为 Br+BrBs/(K-1),显然应该选块数小的表作为外表。
基于代价的优化
启发式规则优化是定性的选择,适合解释执行的系统。解释执行的系统,优化开销包含在查询总开销之中,编译执行的系统中查询优化和查询执行是分开的。因此,可以采用精细复杂一些的基于代价的优化方法。
统计信息
基于代价的优化方法要计算查询的各种不同执行方案的执行代价,它与数据库的状态相关,优化器需要的统计信息主要包括:
- 对每个基本表,该表的元组总数(N)、元组长度(l)、占用的块数(B)、占用的溢出块数(BO)。
- 对基表的每个列,该列不同值得个数(m)、该列最大值、最小值,该列上是否已经建立了索引、那种索引(B+树索引、Hash 索引、聚集索引)。可以计算选择率(f):如果不同值分布均匀,f = 1/m;如果不同值得分布不均匀,每个值得选择率,f = 具有该值得元组数/N。
- 对索引,索引层数(L)、不同索引值的个数、索引的选择基数 S(有 S 个元组具有某个索引值)、索引的叶结点数(Y)。
代价估算示例
全表扫描算法的代价估算公式
- 如果基本表大小为 B 块,全表扫描算法的代价 cost = B;
- 如果选择条件是“码 = 值”,那么平均搜索代价 cost = B/2。
索引扫描算法的代价估算公式
- 如果选择条件是“码 = 值”,则采用该表的主索引。若为 B+树,层数为 L,需要存取 B+树种从根结点到叶结点 L 块,再加上基本表种该元组所在的那一块,所以 cost = L+1。
- 如果选择条件涉及非码属性,若为 B+树索引,选择条件是相等比较,S 是索引的选择基数(有 S 个元组满足条件),满足条件的元组可能会保存在不同的块上,所以(最坏的情况)cost = L+S。
- 如果比较条件是 >,>=,<,<= 操作,假设有一半元组满足条件,就要存取一半的叶结点,通过索引访问一半的表存储块,cost = L+Y/2+B/2,如果可以获得更准确地选择基数,可以进一步修正 Y/2 与 B/2。
嵌套循环连接算法的代价估算公式
- 嵌套循环连接算法的代价:cost = Br+BrBs/(K-1)。
- 如果需要把连接结果写回磁盘,则代价为 cost = Br+BrBs/(K-1)+(Frs*Nr*Ns)/Mrs。其中 Frs 为连接选择性(join selectivity),表示连接结果元组数的比例;Mrs 是存放连接结果的块因子,表示每块中可以存放的结果元组数目。
排序-合并连接算法的代价估算公式
- 如果连接表已经按照连接属性排好序,则代价为:cost = Br+Bs+(Frs*Nr*Ns)/Mrs。
- 如果必须对文件排序,还需要在代价函数中加上排序的代价,对于包含 B 个块的文件排序的代价大约为:(2*B)+(2*B*log2B)。
查询计划的执行
查询优化完成后,关系数据库管理系统为用户查询生成一个查询计划,该查询计划可分为自顶向下和自底向上。
- 自顶向下:系统反复向查询计划顶端的操作符发出需要查询结果元组的请求,操作符收到请求后,试图计算下一个(几个)元组并返回这些元组。
- 自底向上:查询计划从叶结点开始执行,叶结点操作符不断地产生元组并放入缓冲区,直到缓冲区满,这时必须等待父操作符将元组从缓冲区取走才能继续执行。