SQL 子查询优化详解( 四 )


幸运的是,SQL 标准中定义的聚合函数 F(col)F(col) 都是 OK 的——它们都满足 F(∅)=F({NULL})F(∅)=F({NULL}),我们只要对 FF 稍加变换就能解决这个问题 。

  • 对于例子一,将 COUNT(*) 替换成一个对非空列(例如主键)的 Count 即可,例如:COUNT(o_orderkey);
  • 对于例子二,需要把 MIN(IF_NULL(o_totalprice, 42)) 分成两步来做:定义中间变量 X,先用 Project 计算 X = IF_NULL(o_totalprice, 42),再对聚合函数 MIN(X) 进行去关联化即可 。
集合运算的去关联化最后一组优化规则用来处理带有 Union(对应 UNION ALL)、Subtract(对应 EXCEPT ALL) 和 Inner Join 算子的子查询 。再强调一遍,我们的指导思想是:尽可能把 Apply 往下推、把 Apply 下面的算子向上提  。
下面的等式中,×× 表示 Cross Join,?R.key?R.key 表示按照 RR 的 Key 做自然连接:r°e1°e2r°e1°e2。和之前一样,我们假设 RR 存在主键或唯一键,如果没有也可以在 Scan 的时候加上一个 。
SQL 子查询优化详解

文章插图
 

SQL 子查询优化详解

文章插图
 
注意到,这些规则与之前我们见过的规则有个显著的不同:等式右边 RR 出现了两次 。这样一来,要么我们把这颗子树拷贝一份,要么做成一个 DAG 的执行计划,总之会麻烦许多 。
事实上,这一组规则很少能派上用场 。在 [2] 中提到,在 TPC-H 的 Schema 下甚至很难写出一个带有 Union All 的、有意义的子查询 。
其他有几个我认为比较重要的点,用 FAQ 的形式列在下面 。
? 是否任意的关联子查询都可以被去关联化?
【SQL 子查询优化详解】可以说是这样的,在加上少量限定之后,理论上可以证明:任意的关联子查询都可以被去关联化 。
证明方法在 [1]、[3] 中都有提及 。以 [1] 中为例,思路大致是:
  1. 对于任意的查询关系树,首先将关联子查询从表达式中提取出来,用 Apply 算子表示;
  2. 一步步去掉其中非基本关系算子,首先,通过等价变换去掉 Union 和 Subtract;
  3. 进一步缩小算子集合,去掉 OuterJoin、ALOJALOJ、A∃A∃、A?A?;
  4. 最后,去掉所有的 A×A×,剩下的关系树仅包含基本的一些关系算子,即完成了去关联化 。
另一方面,现实世界中用户使用的子查询大多是比较简单的,本文中描述的这些规则可能已经覆盖到 99% 的场景 。虽然理论上任意子查询都可以处理,但是实际上,没有任何一个已知的 DBMS 实现了所有这些变换规则 。
? HyPer 和 SQL Server 的做法有什么异同?
HyPer 的理论覆盖了更多的去关联化场景 。例如各种 Join 等算子,[3] 中都给出了相应的等价变换规则(作为例子,下图是对 Outer Join 的变换) 。而在 [1] 中仅仅是证明了这些情况都可以被规约到可处理的情形(实际上嘛,可想而知,一定是没有处理的) 。
SQL 子查询优化详解

文章插图
 
另一个细节是,HyPer 中还存在这样一条规则:
SQL 子查询优化详解

文章插图
 
其中,D=ΠF(T2)∩A(T1)(T1)D=ΠF(T2)∩A(T1)(T1),表示对 T1T1 的 Distinct Project 结果(所谓的 


推荐阅读