数据库查询编译原理
查询编译的三个步骤:
SQL -> AST树(抽象语法树)-> 逻辑查询计划 -> 物理执行计划
1、SQL -> AST树
对使用诸如SQL的某种语言书写的查询进行语法分析,即将查询语句转换成按某种有用方式表示查询语句结构的语法树。
2、AST树 -> 逻辑查询计划
把语法分析树转换成关系代数表达式树。
SQL -> 分析器 --> AST树 -> 预处理器 -> 逻辑查询计划生成器 -> 查询重写器 -> 逻辑查询计划
3、逻辑查询计划 -> 物理执行计划
物理查询计划不仅指明了要执行的操作,而且也找出了这些操作执行的顺序、执行每步所用的算法、获得所存储数据的方式以及数据从一个操作传递给另一个操作的方式。
第一个步骤有一个确定的结果,后面两个步骤涉及多种选择。在挑选一个逻辑查询计划时,我们有机会应用多个不同的代数运算,目标是得到最佳的逻辑查询计划。当从一个逻辑计划产生物理计划时,有多种可能的执行方式,我们必须估计每个可能选项的预计代价。
1 语法分析
将类似SQL这样的语言编写的文本转换成语法分析树。
如可将SQL语句“ SELECT movieTitle From StarsIn,MovieStar Where starName = name AND birthdate LIKE '%1960' ”转换成如下语法树:
2 从语法分析树到逻辑查询计划
讲语法树转换成所希望的逻辑查询,需要分两步:
a) 按适当的群组用一个或多个关系代数运算符替换语法树上的节点与结构,生成关系代数表达式。
b) 利用代数定律,将第一步产生的关系代数表达式转换成我们所期望的一个表达式,它将被转换成物理查询计划。
2.1 转换成关系代数
关系代数表达式自底向上由以下内容组成:
a)将<FromList>中提及的全部关系进行积运算,运算结果是选择运算符的参数。
b)选择运算符,其中C就是要被替换的<Condition>表达式,同时选择的结果又是投影运算符的参数。
c)投影,其中L是<SelList>中的属性列表。
考虑第1节中的语法分析树,按照上述规则将其转换成代数表达式树,我们取<FromList>中的两个关系StarsIn与MovieStar的积,用<Condition>中的两个条件starName=name和birthdate Like ‘%1960’进行选择,并投影到<SelList>中的属性movieTitle上,所得的关系代数表达式为:
2.2 从条件中去除子查询
对于<Condition>中包含子查询的语法树,我们将引入运算符的中间形式,它介于语法分析树的语法类与作用到关系上的关系代数运算符之间。该运算符通常被称为两参数选择,通常用标签 表示语法树中的两参数选择。该节点之下有一个左子节点,它表示要对其做选择运算的关系R,以及一个右子节点,它表示作用到关系R的每个元组上的条件表达式。
如可将SQL语句“ SELECT movieTitle From StarsIn Where starName IN(SELECT name FROM MovieStar WHERE birthdate LIKE '%1960') ”的语法树进行重写:
================》
在重写过程中进行了多种变换:
a)子查询被一个关系代数表达式所替换。
b)外层查询表达式也已经进行了替换,不过我们将外层的选择表示为两参数选择,而不是常规的关系代数运算符。
c)形如t IN S的<Condition>将被替换成一个笛卡尔积和一个单参数选择:
d)最终,这个运算符的中间形式将被重写为完整的关系表达式:
子查询相关
当子查询相关时,将子查询翻译成关系代数的策略更为复杂。对于查询“SELECT DISTINCT m1.movieTitle, m1.movieYear FROM StarsIn m1 WHERE m1.movieYear - 40 <= (SELECT AVG(birthdate) FROM StarsIn m2, MovieStar s WHERE m2.starName = s.name AND m1.movieTitle = m2.movieTitle AND m1.movieYear = m2.movieYear)”,有如下语法树中间形式:
由于子查询是相关的,无法从子查询所涉及的关系StarsIn m2与MovieStar中获得属性m1.movieTitle和m1.movieYear,因此需要将选择进行上移。将中间形式转换为代数表达式为:
2.3 逻辑查询计划的改进
当我们把查询语句转换为关系代数时,我们获得了一个可能的逻辑查询计划,下一步是根据代数定律重写计划。我们可能产生多个逻辑计划,表示不同的运算符顺序或组合。具体详见第3节。
2.4 可结合/可分配的运算符的分组
将子树中相同的可结合/可分配的运算节点,组合成具有多部分参与的单个运算节点。
3 用于改进查询计划的代数定律
在得到语法树以后,我们需要将其转换成一个表达式,该表达式由关系代数运算符组成。应用启发式规则,可以使用一些代数定律来改进语句中的代数表达式,从而生成更有效的物理查询计划。应用这些代数变换式的结果是逻辑查询计划,它是查询重写阶段的输出。
3.1 交换律与结合律
关系代数的多个运算符同时满足结合律和交换律:
3.2 分解定律
(可选)
3.3 下推选择
由于选择可以明显减少关系的大小,因此只要不改变表达式的结果,就把选择在语法树上尽可能地下移。然而,当查询包含虚视图时,有时先将选择尽可能往树的上部移是很必要的,然后再把选择下推到所有可能的分支。
选择下推到每个参数可能是可选的或必须的:
1、对于并,选择必须下推到两个参数中。
2、对于差,选择必须下推到第一个参数,下推到第二个参数是可选的。
3、对于其他运算符,只要求选择下推到其中一个参数。
3.4 下推投影
投影可以像选择一样下推到多个其他运算中。下推投影不如下推选择那么有用,因为投影不改变元组数,只减少元组的长度。
3.5 有关连接与积的定律
计算连接的算法通常比计算积之后跟着一个对该积的结果的选择的算法要快的多。
3.6 有关消除重复的定律
消除重复下推可减少中间关系的大小。
3.7 涉及分组与聚集的定律
3.8 利用代数定律改进逻辑查询计划
上面提到了很多代数定律,其中优化器最常用到的有:
a)选择尽可能地下推。如果一个选择条件是多个条件的AND,则我们可以把该条件分解并分别将每个条件下推。
b)投影也可被下推到树种。
c)消除重复有时可以省去,或移到树中更方便的位置。
d)某些选择可以与其下面的积相结合,以便把运算对转换成等值连接。一般计算等值连接比先算积再选择要高效的多。
下面将举两个应用代数定律改进逻辑查询的计划。
a)对于上面子查询相关的例子,我们可以做出以下优化。
由于m1只有movieTitle和movieYear两个属性被投影,且这两个属性和m2等值连接,因此可以分别用m2.movieTitle和m2.movieYear替换m1.movieTitle和m1.movieYear。因此,上部的连接是不必要的,可推导出优化后的代数表达式为:
b)考虑下述查询
我们可以把选择的两部分分解成和,后者可以下推到右子树中,因为所涉及的属性birthdate只来自于关系MovieStar。第一个条件涉及积的两边的属性,可以将他们和积转换成等值连接,结果如下所示:
4 运算代价的估计
这一部分我们把逻辑计划转换成物理计划。通常由逻辑计划派生而得到多个不同的物理计划,并对每个物理计划进行评估,通常称为基于代价的枚举,我们选择具有最小估计代价的物理查询计划。
由逻辑计划派生物理查询计划时,需要考虑以下几个方面:
a)对于满足结合律和分配律的运算,我们需要考虑它们的结合和分配方式。
b)每个运算符要使用的算法,例如,决定使用嵌套循环连接或散列连接。
c)其他运算符,如扫描、排序等,它们是物理计划所需要的但在逻辑计划中都不显示地存在的。
d)数据从一个运算符传送到下一个运算符的方式,例如,通过在磁盘上保存的中间结果,还是使用迭代算子每次传送一个元组或一个主存缓冲区。
估计物理计划的代价是基于数据的参数来做的,这些参数要么精确地由数据计算而得,要么通过统计量收集过程来估计。
4.1 中间关系大小的估计
中间关系的大小对开销有重大的影响。
4.2 投影运算大小的估计
投影运算的结果大小可以精确计算,通常投影时元组大小缩减,因为某些成分被消除。然而扩展投影允许产生新的成分,它们是已有属性的组合,因此存在投影运算后,关系大小增加的情况。
4.3 选择运算大小的估计
对于大小的估计,有。
对于,假设典型的不等值比较将返回约三分之一的元组,有。
对于,建议假设所有的元组将满足这个条件,即。
当选择条件C是多个等值与不等值比较的AND时,可以把它作为多个简单选择的级联,即对于,有估计。一个特别的情况是当条件矛盾时,我们的分析将失效。例如,考虑,由于a=10和a>20矛盾,估计的结果将为。对于,如果R中有n个元组,其中有m1个满足C1,有m2个满足C2,则估计S中元组的数目为:n(1-(1-m1/n)(1-m2/n))。
4.4 连接运算大小的估计
具体详见《数据库系统实现》5.4.4小节
4.5 多连接属性的自然连接
具体详见《数据库系统实现》5.4.5小节
4.6 多个关系的连接
具体详见《数据库系统实现》5.4.6小节
4.7 其他运算大小的估计
a)并
建议采取较大者加上较小者的元组数目之和的一半。
b)交
建议取较小者元组数目的一半。
c)差
建议取平均值:T(R) - T(S) / 2
5 基于代价的计划选择
无论是选取一个逻辑查询计划还是从一个逻辑计划构造一个物理计划,查询优化器都需要估计特定表达式的代价。基于代价的计划选择中最重要也是最为困难的问题:多个关系的连接次序与选择。
计算表达式的代价可用所执行的磁盘I/O数来加以近似。磁盘I/O受以下因素影响:
a)所选取用于实现查询的特定逻辑运算符。
b)中间关系的大小。
c)用于实现逻辑运算符的物理运算符,比如对给定关系是否加以排序的选择。
d)相似运算的排序。
e)由一个物理运算符向下一个物理运算符传递的参数。
5.1 大小参数估计值的获取
上一节中的公式是以知道某些重要参数为基础的,特别是T(R),V(R,a),现代DBMS一般允许用户或管理员显式地要求统计信息的收集。R所占用的块数B(R)可通过实际所用块数计数或通过T(R)除以一个磁盘块可以容纳的R的元组个数。
此外,DBMS可以计算一个给定属性诸值的直方图。如果V(R,a)不是太大,则该直方图由具有属性a的每个值的元组数目组成。如果这个属性存在大量不同值,则最常出现的值被单独记录,而其他值则分组统计。使用直方图的一个优点是连接的大小估计比上一节中的简化方法更准确。
下面举两个例子:
a)考虑计算连接,令R.b的直方图是:
1:200,0:150, 5:100,其余值:550
令S.b的直方图是:
0:100, 1:80, 2:70,其余值:250
根据R和S的直方图,我们可以计算出R的150个b=0的元组和S的100个具有相同b值得元组连接结果有15000个元组。类似的,200个b=1的R元组与80个b=1的S元组连接结果有16000个元组。
b)考虑如下查询,“SELECT Jan.day, July.day FROM Jan,July WHERE Jan.temp = July.temp”,这个查询计划是对Jan与July在温度上做等值连接,然后投影到每个day属性上。
如果相应的带宽分别有T1与T2个元组,且这个带宽的宽度是V,则在这些带宽上连接的元组数估计是T1T2/V。在这个例子中,满足条件的带宽是40~49和50~59,因此40~49带宽产生10*5/10 = 5个元组,而50~59带宽则产生5*20/10 = 10个元组。综上,这个连接大小估计是5+10=15个元组。
如果我们没有直方图,只知道每个关系有245个元组分布于0~99的100个值之中,则我们对连接大小的估计是245*245/100=600个元组。
5.2 统计量的计算
统计量通常仅是周期性地计算,首先,因为在短时间内不会发生剧烈变化;第二,即使不太准确的统计量也是有用的;第三,实时更新统计量开销太大。
统计量的更新会在一段时间后或一定数目的更新后自动被触发,也可当DB管理员注意到性能很差的查询计划经常被查询优化器选中时,人工触发。
为整个关系计算统计量可能会开销很大,一个通用的方法是通过取样小部分数据来计算大约的统计量。
5.3 减少逻辑查询计划代价的启发式估计
逻辑查询优化过程中,对一个查询计划转换前与转换后的代价进行估计,当转换后代价更低时我们就应用这个转换,否则避免使用这个转换。
我们正在评估逻辑查询计划的代价,此时我们还没有决定使用哪些将被用于实现关系代数操作符的物理操作符,因此估计不能建立在磁盘I/O上。
下面对一个逻辑查询计划进行优化:
令关系R与S的统计量为 ,则将选择和去重下推,有如下转换:
=======》,
5.4 枚举物理计划的方法
穷尽法,将第4节开头列出的四种选择加以组合(连接的次序、运算符的物理实现等),每个可能的物理计划被赋予一个估计的代价,选择代价最小的一个计划。
搜索物理计划空间的两个主要方法:
a)自顶向下:从逻辑查询计划树的根部开始向下进行。对于根节点的运算的每个可能的实现,我们考虑计算其参数的每种可能的方法,并计算每种组合的代价,取最优的一个。
b)自底向上:从逻辑查询计划树的子表达式开始,计算该子表达式的所有可能方式的代价,并按所有可能的方式与上一级运算符相结合。
实际上,两种方法没有太大区别。在计算每个运算符时取最优的一个,不能保证整体最优,尽管它常常可获得最优。有一些优化方法,可以对于某些子表达式不是最优的计划中得到总体上最优的计划。下面将介绍几种优化算法。
启发式选择
从连接两个具有最小估计大小的关系开始,然后对这个连接结果以及与连接的关系集合重复这个过程。下面是一些常用的启发式规则:
a)如果逻辑计划需要在关系R上进行A=c的选择,且在属性A上有索引,则只需一个索引扫描;
b)如果连接运算的属性上有索引,则采用索引连接;
c)如果连接运算的属性是排序的,则采用排序连接比索引连接好,尽管结果可能未必比索引连接好;
d)当计算三个或以上关系的并或交时,先对最小关系进行组合。
分支界定计划枚举
首先通过启发式选择找到一个好的物理计划,令其代价为C。然后考虑其中的一个子查询,希望找到一个其他计划,使得整体查询计划小于C。
这种方法的一个重要好处在于我们可以判定何时终止搜索并得到目前为止最优的计划。例如,如果代价C较小,即便可以发现更好的计划,但为找到这些计划开销较大,则没有意义。但是如果C较大,则花点时间希望找到一个更快的计划是明智的。
爬山法
从一个启发式方法选定的物理计划开始,我们可以对计划做小的修改,如用另一种方法替换执行一个运算符的一个方法,或通过使用结合律或交换律对连接重新排序,找到具有较低代价的“领近”计划。
动态规划
在自底向上对逻辑查询树进行物理实现时,仅对每个子表达式保留最佳的执行计划。
Selinger风格的优化
这个方法改进了动态规划方法,不仅记录了每个子表达式的最小代价的计划,而且也记录了那些虽然具有较高代价但对计划树中较高层很有用的计划。
例如,当一个计划有较高的代价,但其对某个属性进行了排序,而计划树较上层有如下操作:
a)在该属性上存在排序的操作;
b)在该属性上存在聚合的操作;
c)在该属性上有连接操作。
则该计划是感兴趣的计划。
6 连接顺序的选择
这一节着眼于为三个或三个以上关系的连接选择顺序。
6.1 连接的左右参数的意义
两个参数关系所代表的意义是不同的,当我们选择一个物理计划时,我们应该假定将连接的左参数作为较小的关系存储在主存的数据结构中,称为构造用关系。每次一块读入连接的右参数,称为探查用关系,并将它的元组与已存储的关系进行匹配。对参数进行不同区分的连接算法包括:
a)嵌套循环连接,在这种连接中我们认为左参数是外部循环关系。
b)索引连接,我们认为这种连接的右参数有索引。
6.2 连接树
当有两个关系的连接时,我们需要对参数排序。对2.3节的例子“SELECT movieTitle FROM StarsIn,MovieStar WHERE starName = name AND birthdate LIKE '%1960'”,有如下逻辑查询计划:
由于每一部电影通常有几个明星,我们假设StarsIn在开始将大于MovieStar,同时,选择符由进一步缩小了右参数的范围,所以应该选择MovieStar作为左参数。
当有两个关系时,连接树只有两种选择,当连接有两个以上的关系时,可能的连接树的数量会迅速增长。比如,对于有四个关系进行连接时,有三种可能的树的形状:
而如果考虑树中关系的参数顺序,每种树形对于n个参数将会有n!种方法对其进行排序,四个参数将会有4!=24种形状。
6.3 左深连接树
一颗二叉树,如果所有右子女都是树叶的话,它就是左深树,上一节中的a图。只考虑左深树树形有以下优点:
a)可以有效减少可能出现的计划树的数目,可用于比较大的查询。
b)左深树可以和通用的连接算法很好地交互,尤其是嵌套连接和一趟连接。
例如,对上一节中的a图进行一趟连接。选择最小的关系作为关系R,并保存在主存中,并且在计算R和S的连接过程中,还需要在主存中保留中间结果。这样,我们需要的主存缓冲区。算出R和S的连接结果后,需要计算其和T的连接,此时R所使用的缓冲区不再需要,可用于存储与T的连接结果,以此类推,以一趟连接方法计算左深树需要的主存空间为两个临时关系所需的空间。
6.4 通过动态规划来选择连接顺序和分组
要为多个关系选择连接顺序,我们有以下三个选择:
a)考虑全部
b)考虑一个子集,如只考虑左深树的情况
c)采用启发式方法选取一个
动态规划的思想是:我们填写一个代价表,只记住我们推出结论所需的最少信息。下面举一个例子来说明:
令有四个关系R、S、T和U进行连接运算,假设每个关系有1000个元组,它们的属性情况如下所示,
对于单个集合,它们的大小、代价如下,由于代价是以中间关系的大小为考量,所以它们的代价为0
现在考虑关系对,因为两个关系的连接,任然没有中间关系,故代价仍为0。由于每个关系的大小都相等,所以没有根据来选择哪一个计划在左,哪一个在右,这里根据字母的顺序选择左参数。
现在考虑三个关系的连接,三个关系可以有四种组合方式,即{R,S,T}、{R,S,U}、{R,T,U},{S,T,U}。由于代价估计等于中间关系的大小,我们希望作为左参数的两个关系的连接结果尽可能小,比如对于{R,S,T}中的关系两两组合,其中S和T组合的连接大小最小,为2000,而R和S为5000,R和T为1000000,所以选择{S,T}的连接为左参数,其他组合方式类似。
最后,考虑四个关系连接的情况,根据上面三个关系,分别进行对第四个关系进行连接。
上表中后面三个是包含浓密树时的额外选择。
在这个例子中,我们看到所有代价中的最小的是第四种连接方式,其代价为3000.
6.5 带有更具体的代价函数的动态规划
利用关系的大小作为代价估计可以简化计算过程,然而这个简化没有考虑物理计划的代价。比如对于同一个连接,R和S的连接,当S的连接属性上有索引时,根据R的属性可以直接找到S中对应的元组。而当S的连接属性上没有索引时,对于R的每一个元组,都要扫描S的每一个元组。
对动态规划算法进行改进并不难,将连接算法考虑进去即可。首先用磁盘I/O作为代价度量,比如,计算R和S连接的代价时,我们将R的代价、S的代价,以及利用可获得的最佳算法对两个关系进行连接所需的最小代价相加,就可以估计出它们更精确的代价。
6.6 选择连接顺序的贪婪算法
采用像动态规划或者分支限制范围的搜索这样的方法,来寻找五、六个关系的最佳连接顺序是合理的,然而当连接数超出范围,则我们可以采用启发式连接顺序,启发式的最普遍的选择是贪婪算法。
在这个算法中,我们希望在树的每一级保持尽可能少的中间关系,并不再寻找其他的可能性。比如,在6.4小节中,在考虑两个关系的连接过程中,我们发现T和U的连接代价最小,为1000,因此在接下来探索三个关系的连接时,就以T和U的连接为左参数,考虑和的代价,由于前者代价为10000,而后者代价为2000,优于前者,故我们选择的连接方式。最后我们得到的连接顺序,其代价为3000。
然而,也存在贪婪算法寻找最佳结果失败,而动态规划算法能保证找到最佳结果的情况,这里根据取舍进行选择。
7 物理查询计划选择的完成
要将逻辑计划变成一个完整的物理查询计划仍需要几个步骤,下面将进行介绍。
7.1 选取一个选择方法
选取一个物理查询计划最重要的步骤之一是为每个选择运算符精选算法。考虑选择,其中R(x,y,z)有以下的参数:T(R) = 5000, B(R) = 200, V(R, x) = 100,以及V(R, y) = 500。另外,令所有的x,y和z都有索引,并且假设R在z的索引上是聚集的。以下是执行这个选择的几种选项:
a)表扫描后进行过滤。因为R是聚集的,所以代价为B(R),200次磁盘I/O。
b)使用x的索引以及索引扫描来找出x=1的元组,然后利用过滤操作来检测y=2以及z<5。由于x索引不聚集,因此有大约T(R)/V(R,x)=50个元组的x=1,大约需要50次I/O。
c)使用y的索引以及索引扫描来找出y=2的元组,然后对这些元组进行过滤来检测x=1以及z<5。由于y索引不聚集,因此有大约T(R)/V(R,y)=10个元组的y=2,大约需要10次I/O。
d)使用z的索引以及索引扫描来找出z<5的元组,然后对这些元组进行过滤来检测x=1以及y=2。磁盘I/O数大约为B(R)/3=67。
我们看到代价最小的计划是第三种,估计代价为10次磁盘I/O。因此,这个选择的最佳物理计划搜索所有y=2的元组,然后为另外两个条件进行过滤。
7.2 选取连接方法
选取连接的几个标准:
a)如果缓冲区有足够的大小,可以保证能够装入做参数或能分少量几次装入做参数,则可以用一趟连接或嵌套循环连接。
b)当一个或两个参数已经在它们的连接属性上排序,或对于同样的属性有两个或多个连接,则排序连接是一个好的选择。例如,其中基于a对R和S进行排序将会引起R和S连接的结果在a上被排序,并且在第二个排序连接中被直接使用。
c)如果一个连接,并且在连接属性S.b上有索引,则应该选择一个索引连接。
d)如果没有机会利用已经排序的关系或索引,并且需要多趟连接,则散列连接也许是最佳选择,因为他所需的扫描次数取决于较小参数的大小,而不是两个参数的大小。
7.3 流水操作与物化
执行一个查询计划的原始方法是对运输进行适当的排序,并且将每个运算的完整结果存储在磁盘上,直到它需要被另一个运算使用,这个策略叫做物化。
还有一个更有效的方法是一次同时交错进行多个运算,不等一个运算完成计算完整的结果,而是当一个运算产生一个或一小部分元组后直接传递给使用它的运算,不需要将中间元组存储在磁盘上,这个方法叫流水操作。
流水操作的优点是节省了磁盘I/O。缺点是在任何时候几个运算必须同时进行,且共享内存,需要占用的内存空间可能较大,会影响算法的选择,可能必须选择磁盘I/O更高的算法,甚至于抵消流水操作所节省的磁盘I/O。
7.4 一元流水运算
选择和投影可以选择使用流水操作,因为这些运算是一次一个元组,不需要有多个块的输入输出。
7.5 二元运算的流水操作
二元运算的结果也可以进行流水操作。我们使用要给缓冲区将结果传递给消费者,一次一块。
对于查询计划,我们假设有以下条件:
a)R占据5000块,S和U各占10000块、
b)中间结果占据k块。
c)使用散列连接或者一趟连接或者两趟连接,取决于k的大小。
d)假设有101个可用的缓冲区。
则在k的不同取值范围下,有如下的物理代价:
运算过程详见《数据库系统实现》第5.7.5小节。
7.6 物理查询计划的符号
扫描运算符
作为逻辑查询计划树的叶子操作对象的每个关系R将被一个扫描运算符所代替:
a)TableSan(R):以任意顺序读入所有存放R的元组的块。
b)SortScan(R,L):按照顺序读入R的元组,并以列L中的属性进行排列。
c)IndexScan(R,C):按照R中某一个属性的索引,来根据条件C来访问R的元组。
d)IndexScan(R,A):按照R中的某一个属性的索引来检索R的元组。
选择运算符
若选择的属性上没有索引,则可以简单地用Filter(C)代替。Filter(C)只适用于中间关系的选择,如果R是一个存储关系或物化关系,则我们必须使用一个运算符(TableScan或者SortScan)来访问R。如果的结果以后将被传递给一个需要其参数被排序的运算符,则我们也许偏向于排序扫描。
当选择的条件为(D是某个其他条件)时,并且有一个R.A上的索引,则我们可以:
a)使用运算符来访问R。
b)使用Filter(D)来代替选择。
排序运算符
一般使用一个显式的物理运算符Sort(L)来对没有排序的一个操作对象关系进行排序。
7.7 物理运算顺序
以从下到上,从左到由的前序遍历的顺序依次执行各子树。使用一个迭代器网络来执行每一颗子树的所有节点。因此,在一颗子树中所有节点的运算符间用GetNext调用来决定事件的确切顺序。
根据这个策略,查询优化现在能够为查询生成执行代码,也许是一个函数调用序列。
8 小结