物化视图通过将预计算的结果存下来,能极大地减少查询的开销和时间。物化视图的查询优化问题是一个视图的匹配问题,即给定一个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条件$P$可以改写成CNF的形式:
$$ P = P_1 \land P_2 \ldots \land P_n $$
我们将每个条件\( P_i \)划分到一下三类中去:
- 等值条件PE: \( T_i.C_j = T_m.C_n \),其中\( i \)和\( m \)可能一样。
- 范围比较PR: \( T_i.C_j\ op\ c \),其中\( op \)是\( \le, \lt, \ge, \gt, = \)几个操作符之一,\( c \)是一个常量。
- 其他条件PU
那么我们可以将selection条件改写为如下形式:
$$ P = PE \land PR \land PU $$
Column Equivalence Class
通过将PE中的条件进行聚类,我们可以得到column equivalence class。举个例子假设PE由以下条件组成:
$$ 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:
- \( C_i, C_j, C_k, C_l \)
- \( C_a, C_b, C_c \)
SPJ Query的改写
base table集相同的场景
我们首先讨论Query的base table集与MV的base table集相同的场景。
MV是否包含Query的全部行?
由于MV和Query的base table集相同,所以我们只需要考虑Query的selection条件\( P_Q \)是否满足MV的selection条件\( P_V \),即
$$ P_Q \implies P_V, \forall t\ if\ P_Q(t)\ is\ true,\ then\ P_V(t)\ is\ true $$
为了能快速进行比较和算出残差条件,我们将上面的条件改为如下三个条件的比较:
- \( PE_Q \implies PE_V \)
- \( PE_Q \land PR_Q \implies PE_V \land PR_V \)
- \( PE_Q \land PU_Q \implies PE_V \land PU_V \)
上述三个条件显然是比\( P_Q \implies P_V \)更严格的,但是改写成这样更便于计算和比较。
针对条件1,我们只需要保证\( PE_V \)中的每个column equivalence class都能在\( PE_Q \)中找到对应的超集,并且很容易计算出补偿条件。例如假设\( PE_Q = (C_a = C_b)\land(C_b=C_c) \),\( PE_V= (C_a=C_b) \),那么这时\( PE_Q \implies PE_V \),并且补偿条件为\( C_b = C_c \)
当条件1的检查通过了之后,我们继续检查条件2。通过\( PE \land PR \)我们可以为每一个column equivalence class计算出一个range,并且Query中的每个column equivalence class的range包含在对应的MV的column equivalence class的range。例如下面的例子:
$$ PR_Q = (C_a > 1)\land(C_b < 2) $$
$$ PR_V = (C_b > 0)\land(C_b < 10) $$
那么Query中的range就是\( (1, 2) \), MV中的range就是\( (0, 10) \),所以条件2成立,并且补偿条件就是\( PR_Q \)。
当条件1和2的检查都通过了之后,我们针对$PU$中的条件进行检查。如果对\( PU_V \)中的条件\( P_{Vi} \),我们在将列引用替换成column equivalence class之后都能在\( PU_Q \)中找到相应的条件\( P_{Qi} \),那么说明条件3得到了满足。而\( PU_Q \)中剩余的无法在\( PU_V \)中找到的条件,则成为补偿条件。例如:
$$ PU_Q = ((C_a + C_d) < 4)\land((C_b \times C_e) < 10) $$
$$ PU_V = (C_b + C_d) < 4 $$
那么我们认为条件3得到了满足,并且补偿条件是\( (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。\( T \)和\( S \)做inner join,要保证\( T \)的duplication factor,join的条件里必须是foreign key和unique key的equi join。我们将这样的join称作cardinality preserving join。
假设Query的base table集是\( T_1, T_2, \ldots, T_n \),MV的base table集是\( T_1,T_2,\ldots,T_n,T_{n+1},\ldots,T_m \)。要检验\( T_{n+1}, \ldots, T_m \) 是否是通过一系列的cardinality preserving join跟\( T_1, \ldots, T_n \)链接起来,我们需要画一个DAG图。其中图的点是base table,并且如果\( T_i \)和\( T_j \)的join条件满足我们上面说到的foreign key和unique key的equi join,则画一根从\( T_i \)到\( T_j \)的线。然后我们逐个删除那些没有出边,只有一个入边的点。如果最后\( T_{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
这个过程如下图所示:
SPJG的改写
在搞清楚了SPJ的改写之后,SPJG的Query改写成使用MV只需要满足如下场景:
- Query的SPJ部分可以使用MV的SPJ部分计算出来
- 补偿条件可以从MV的output表达式中计算出来
- MV的grouping列是Query的grouping列的一个子集
- 如果需要进一步的group by,则进一步的group by所需要的列应该能从MV的输出中计算出来
- 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
参考文献
- Materialized Views In Oracle, 1998
- Answering Complex SQL Queries Using Automatic Summary Tables, 2000
- Optimizing Queries Using Materialized Views: A Practical, Scalable Solution, 2001