查询优化中的物化视图改写(一):SPJG的改写

1,386 阅读6分钟

物化视图通过将预计算的结果存下来,能极大地减少查询的开销和时间。物化视图的查询优化问题是一个视图的匹配问题,即给定一个Query和一系列的物化视图,我们需要找到与之匹配的物化视图,并从物化视图中计算出相应的结果。

注意这里的Query不是指的一条SQL对应的整个Query,而是指其中的一部分,也包括查询优化过程中生成的各种等价的变换。

问题描述

首先,我们假设优化器是cascades style的transformation based的优化器,这个也是目前CBO优化其的主流pattern。

其次,我们主要考虑的问题是怎么使用单个的View来计算出Query。对于从多个View来计算Query一般有2中情况:通过join或者union。多个view来join的情况,由于优化器针对原始Query做各种变换,因此会最终还是会落到单个View来计算。而通过Union多个View的情况则比较复杂,通用的算法比较难,只能针对单独的case来落地,比如时间窗口等。

selection条件的改写

SPJ Query: A query which consists of selection, projection, and inner joins of base tables. By base table we mean direct reference to table, derived table from subquery not included.

SPJG Query: A SPJ query with a final group by operation.

SPJ Query通常可以看作先进行笛卡尔积,后面进行selection和projection操作。为了便于后续的计算,我们将对selection的条件进行一些改写。首先selection条件PP可以改写成CNF的形式:

P=P1P2PnP = P_1 \land P_2 \ldots \land P_n

我们将每个条件PiP_i划分到一下三类中去:

  • 等值条件PE: Ti.Cj=Tm.CnT_i.C_j = T_m.C_n,其中iimm可能一样。
  • 范围比较PR: Ti.Cj op cT_i.C_j\ op\ c,其中opop,<,,>,=\le, \lt, \ge, \gt, =几个操作符之一,cc是一个常量。
  • 其他条件PU

那么我们可以将selection条件改写为如下形式:

P=PEPRPUP = PE \land PR \land PU

Column Equivalence Class

通过将PE中的条件进行聚类,我们可以得到column equivalence class。举个例子假设PE由以下条件组成:

PE=(Ci=Cj)(Cj=Ck)(Ci=Cl)(Ca=Cb)(Cb=Cc)PE=(C_i=C_j)\land(C_j=C_k)\land(C_i=C_l)\newline (C_a=C_b)\land(C_b=C_c)

那么我们可以得到2个column equivalence class:

  • Ci,Cj,Ck,ClC_i, C_j, C_k, C_l
  • Ca,Cb,CcC_a, C_b, C_c

SPJ Query的改写

base table集相同的场景

我们首先讨论Query的base table集与MV的base table集相同的场景。

MV是否包含Query的全部行?

由于MV和Query的base table集相同,所以我们只需要考虑Query的selection条件PQP_Q是否满足MV的selection条件PVP_V,即

PQ    PV,t if PQ(t) is true, then PV(t) is trueP_Q \implies P_V, \forall t\ if\ P_Q(t)\ is\ true,\ then\ P_V(t)\ is\ true

为了能快速进行比较和算出残差条件,我们将上面的条件改为如下三个条件的比较:

  1. PEQ    PEVPE_Q \implies PE_V
  2. PEQPRQ    PEVPRVPE_Q \land PR_Q \implies PE_V \land PR_V
  3. PEQPUQ    PEVPUVPE_Q \land PU_Q \implies PE_V \land PU_V
💡 上述三个条件显然是比$P_Q \implies P_V$更严格的,但是改写成这样更便于计算和比较。

针对条件1,我们只需要保证PEVPE_V中的每个column equivalence class都能在PEQPE_Q中找到对应的超集,并且很容易计算出补偿条件。例如假设PEQ=(Ca=Cb)(Cb=Cc)PE_Q = (C_a = C_b)\land(C_b=C_c)PEV=(Ca=Cb)PE_V= (C_a=C_b),那么这时PEQ    PEVPE_Q \implies PE_V,并且补偿条件为Cb=CcC_b = C_c

当条件1的检查通过了之后,我们继续检查条件2。通过PEPRPE \land PR我们可以为每一个column equivalence class计算出一个range,并且Query中的每个column equivalence class的range包含在对应的MV的column equivalence class的range。例如下面的例子:

PRQ=(Ca>1)(Cb<2)PR_Q = (C_a > 1)\land(C_b < 2)
PRV=(Cb>0)(Cb<10)PR_V = (C_b > 0)\land(C_b < 10)

那么Query中的range就是(1,2)(1, 2), MV中的range就是(0,10)(0, 10),所以条件2成立,并且补偿条件就是PRQPR_Q

当条件1和2的检查都通过了之后,我们针对PUPU中的条件进行检查。如果对PUVPU_V中的条件PViP_{Vi},我们在将列引用替换成column equivalence class之后都能在PUQPU_Q中找到相应的条件PQiP_{Qi},那么说明条件3得到了满足。而PUQPU_Q中剩余的无法在PUVPU_V中找到的条件,则成为补偿条件。例如:

PUQ=((Ca+Cd)<4)((Cb×Ce)<10)PU_Q = ((C_a + C_d) < 4)\land((C_b \times C_e) < 10)
PUV=(Cb+Cd)<4PU_V = (C_b + C_d) < 4

那么我们认为条件3得到了满足,并且补偿条件是(Cb×Ce)<10(C_b \times C_e) < 10

从MV中过滤出Query需要的行

在上一步中我们不仅已经计算出了selection是否得到了满足,也计算出了selection的补偿条件。那么这个问题就是能否从MV的Output中计算出补偿条件。注意这里用到的column equivalence class的概念,MV的输出中只需要包含column equivalence class的一列即可。

从MV中计算出Query需要的行

这个跟上一步中的提到的问题类似,不过上一步中计算的是selection条件,但是这一步计算的是Query需要的表达式。

base table集不同的场景

这里我们不考虑MV的base table集是Query base table集的真子集的场景,因为在优化器的变换过程中这个总能转化为base table集相同的场景。这里我们主要考虑MV的base table集是Query的base table集的真超集的场景。这里除了要考虑上面描述的三个问题之外,我们要回答的另一个问题是MV的如何保持Query所需要的行的duplication factor。TTSS做inner join,要保证TT的duplication factor,join的条件里必须是foreign key和unique key的equi join。我们将这样的join称作cardinality preserving join。

假设Query的base table集是T1,T2,,TnT_1, T_2, \ldots, T_n,MV的base table集是T1,T2,,Tn,Tn+1,,TmT_1,T_2,\ldots,T_n,T_{n+1},\ldots,T_m。要检验Tn+1,,TmT_{n+1}, \ldots, T_m 是否是通过一系列的cardinality preserving join跟T1,,TnT_1, \ldots, T_n链接起来,我们需要画一个DAG图。其中图的点是base table,并且如果TiT_iTjT_j的join条件满足我们上面说到的foreign key和unique key的equi join,则画一根从TiT_iTjT_j的线。然后我们逐个删除那些没有出边,只有一个入边的点。如果最后Tn+1,,TmT_{n+1}, \ldots, T_m都被删除了,那么我们认为检查通过。

💡 我们在画线的时候应该考虑到column equivalence class进行替换

我们以如下的查询为例,假设Query是

Select l_orderkey, l_partkey, l_quantity From lineitem
Where l_orderkey between 1000 and 1500
  And l_shipdate = l_commitdate

而MV是

Create view v3 with schemabinding as Select c_custkey, c_name, l_orderkey,
l_partkey, l_quantity
From dbo.lineitem, dbo.orders, dbo.customer Where l_orderkey = o_orderkey
  And o_custkey = c_custkey
  And o_orderkey >= 500

这个过程如下图所示:

Untitled_Diagram.drawio.png

SPJG的改写

在搞清楚了SPJ的改写之后,SPJG的Query改写成使用MV只需要满足如下场景:

  1. Query的SPJ部分可以使用MV的SPJ部分计算出来
  2. 补偿条件可以从MV的output表达式中计算出来
  3. MV的grouping列是Query的grouping列的一个子集
  4. 如果需要进一步的group by,则进一步的group by所需要的列应该能从MV的输出中计算出来
  5. Query的输出表达式应该能从MV的输出表达式中计算出来

假设我们由如下的MV:

Select o_custkey, count(*) as cnt
sum(l_quantity*l_extendedprice)as revenue From dbo.lineitem, dbo.orders
Where l_orderkey = o_orderkey
Group by o_custkey

而Query如下:

Select c_nationkey, sum(l_quantity*l_extendedprice)
From lineitem, orders, customer
Where l_orderkey = o_orderkey
  And o_custkey = c_custkey
Group by c_nationkey

Query经过改写可以写成如下的形式:

Select c_nationkey, sum(rev)
From customer,
(select o_custkey, sum(l_quantity*l_extendedprice) as rev
      From lineitem, orders
      Where l_orderkey = o_orderkey
      Group by o_custkey) as iq
Where c_custkey = o_custkey
Group by c_nationkey

通过将子查询用MV替换,可以改写成如下形式:

Select c_nationkey, sum(revenue) From customer, v4
Where c_custkey = o_custkey Group by c_nationkey

参考文献

  1. Materialized Views In Oracle, 1998
  2. Answering Complex SQL Queries Using Automatic Summary Tables, 2000
  3. Optimizing Queries Using Materialized Views: A Practical, Scalable Solution, 2001