蓝盟IT小贴士,来喽!
子查询(Subquery )优化一直是SQL查询优化中的难点之一。 关联子查询的基本执行方法类似于Nested-Loop,但这种执行方法往往效率不高,无法承受。 如果数据量稍大,则必须用优化程序取消关联(Decoorelation或Unnesting ),然后改写为更有效的操作符,如Semi-Join。
前人已经总结了完整的方法论,理论上可以将任意一个查询非关联化。 本文将结合SQL Server和HyPer的几篇经典论文,从浅到深地阐述这一关联过程中所涉及的理论体系。 他们用的方法大同小异,基本思想相通。
本论文的例子都基于TPC-H的表结构,这里有参考用的。
子查询概述
子查询是SQL标准中定义的语法,几乎可以存在于SQL的任何地方。 其中包括SELECT、FROM、WHERE等子句。
一般来说,子查询可以分为相关子查询(Correlated Subquery )和非相关子查询(Non-correlated Subquery )。 后者的非相关子查询是个简单的问题,最简单的说,执行它,得到结果集,将其物化后,再执行外层查询即可。
除非另有说明,否则不相关的子查询不在本文范围内。 以下子查询是指相关的子查询。
相关子查询的特殊之处在于它本身是不完整的。 闭包包含外部查询提供的参数。 当然,您只能在知道这些参数的情况下执行查询,因此不能像处理非相关子查询那样处理它们。
子查询可以根据生成的数据分类如下。
标量子查询:输出一行一列的结果表。 这个标量值就是结果。 如果结果为空(0行),则输出NULL。 但是,请注意,不允许超过一行的结果,会发生运行时异常。
“存在检查”子查询:标识EXISTS子查询并返回布尔值。 如果出现在WHERE上,这就是我们熟知的Semi-Join。 当然,它也会出现在可以放布尔值的地方。
“集合比较”子查询:特别指IN、SOME和ANY查询,并返回布尔值。 典型的格式是x=SOME(Q ) (等于x输入q )或x小于全部(q ) (等于xnotin )。 同样,有时也会出现在可以放布尔值的地方。原始的执行计划
以Query 1为例,可以直观地感觉到为什么需要相关子查询的去相关化。
以下是Query 1中的原始非关联查询计划(Relation Tree )。 与其他查询计划不同,我们特意画了表达式树(Expression Tree )。 可以看到子查询实际上悬挂在Filter表达式下。
img实际运行时,查询计划执行机构(Executor )在Filter上运行时调用表达式执行机构(Evaluator )。 由于此表达式包含标准量子查询,因此Evaluator将调用Executor计算标准量子查询的结果。
此执行程序-执行程序的交替调用效率非常低! 考虑到Filter中可能会有数百万行的数据通过,如果对每行的数据执行子查询,则查询的总执行时间显然是不可接受的。
应用运算符
上述称为Relation - Expression - Relation的交替引用不仅性能令人担忧,对优化程序来说也是麻烦的存在——我们的优化规则进行匹配来变换Relation,这里的子
因此,在开始关联之前,要引入Apply操作员。
虽然Apply操作符(也称为Correlated Join )接收两个关系树的输入,但与普通的Join不同,Apply的Inner输入(在图中为右侧子树)是一个带参数的关系树。
Apply的含义由下图右半部分的集合表达式定义。 对于外部关系RR中的每个数据RR,计算内部关系E(r )和E(r ),并输出它们连接的结果re(r )和re(r )。 Apply的结果是所有这些结果的并集(本文所说的并集是指Bag语义下的并集,即UNION ALL )。
文/上海蓝盟 IT外包专家