摘要:典型的查询优化器只使用二路连接(binary join)枚举计划,这样数据库很容易产生次优的计划。本文介绍了SAP HANA系统中的多路连接(m-way join)感知优化器。扩展现有查询优化器最简单的方法就是在传统的二路连接枚举的框架上枚举m-way join。然而有许多不同的二进制连接(binary join)可以表示相同的多路连接(m-way join)。因此可能会造成不必要的枚举,为解决这个问题,本文提出了一种新的算子,引入了 m-way join unit 这个概念,并解释了如何将心这个新的算子构造嵌入到SAP HANA查询优化器当中。还利用m-way连接单元的特性进行了优化器的增强。
contributions:
- Cost-based m-way join enumeration:本文提出了一种多路连接感知优化器,这种优化器考虑了多种m-way连接算法和group-by在物理/逻辑枚举期间pushdown和eager aggregation。本文重点讨论了在基于代价的枚举中协调各种m-way连接算法和group-by运算符的全局优化问题,以及如何协调每个m-way连接算法的局部优化问题。
- Single enumeration framework for both m-way and binary join algorithms:优化器支持行表的binary join,同时支持列表的m-way 连接,以及单个枚举框架中行和列的混合连接
- Easy extension of transformation-based enumeration:本文介绍了如何扩展现有优化器,支持m-way连接算法。
SAP HANA中的现有 m-ways join 方法
本节介绍了SAP HANA中可用的m-way连接算法。SAP HANA是一种内存数据库,同时支持行存和列存。SAP HANA在列式数据结构的基础上,引入了字典来优化存储与运算性能。
字典编码
下图展示了列表的字典编码。图2(b)显示了使用字典编码的列表表示。每列由两个数组组成,即value id(vid)数组和value数组。vid数组为每条记录存储一个值id。value 数组将vid映射为值。这种编码方式可以很好的压缩数据,节省存储空间,而且查找速度也很快。
举个栗子:
下图是 Factor 表Sales和Dimension表Date和Customer的星形连接查询案例。图中查询计算了2019年来自德国客户的总收入。
这样的存储方式使得join就变成了简单的数组操作。代码执行如下:
sum = 0;
for i in |Sales|
d_id = Sales.d_id[i];
c_id = Sales.c_id[i];
if (Date.Y[d_id] == 2019)
if (Customer.nation[c_id] == ’DE’)
sum += Sales.amount[i];
M-way Star Join
但是并不是所有的数据都能够按列排序,而且能够紧密压缩。因此 SAP 引入了基于字典的联接索引(DJI)。DJI有两种变体:
- per dictionary:增量构建的,缓存以供后续使用
- per query:为动态查询构建的,使用完就丢弃
现有m-ways star join,其Fact table F,m-1 个 Dimension tables, D1,D2, …, Dm−1。其时间复杂度为:
O
(
∣
F
∣
+
∣
D
1
∣
l
o
g
∣
D
1
∣
+
.
.
.
+
∣
D
m
−
1
∣
l
o
g
∣
D
m
−
1
∣
)
O(|F| + |D1|log|D1| + ... + |Dm−1|log|Dm−1|)
O(∣F∣+∣D1∣log∣D1∣+...+∣Dm−1∣log∣Dm−1∣)
假设m-way star join cost为:
a
M
∣
F
∣
+
b
M
∣
D
∣
l
o
g
∣
D
aM|F| + bM|D|log|D
aM∣F∣+bM∣D∣log∣D
hash join cost为:
a
h
∣
F
∣
+
b
h
∣
D
ah|F|+bh|D
ah∣F∣+bh∣D
当|D|很小时,m-way star join更加有效,成本更低。
M-way Column Join
m-way column join可以有效的减少中间结果的生成。下图为R ⨝ S ⨝ T的两种连接算法的比较,连接条件:R.x = S.x and S.y = T.y。
假设:|R| < |S| < |T|,|R ⨝ S ⨝ T| < |R ⨝ S| < |R|说明每个连接的中间结果减少。
优点:
- 没有将中间临时表materialize
- 通过进行半联接减少索引的条目
- 它的索引操作,构建和查找,比哈希表操作更高效。不需要生成哈希键,不存在哈希冲突,并且不必存储索引键。
缺点:增加更多的索引查找操作
m-way join感知优化
M-way Join Unit
本文定义了一个新的算子 join unit ,存储m-ways join 的连接图。 m = 1 or 2都可是视为特殊的m-ways join unit。本文的重点在处理 join 算子和 group-by 算子,filter 算子等被pushdown到目标 table 中进行处理。join unit 中包含filter, orderby, and limit 等算子,以及join and group-by算子。以下将不再对其进行进一步描述。
通过以下三个构建规则将较小的 join unit 组合成较大的 join unit :
- CombineBaseTable(Tc):基表Tc
- CombineJoin(⨝c, Ci1, Ci2):两个join unit进行组合
- CombineGroupBy(γc, Ci): join unit和 group-by 组合
本文引入了一种非常重要的启发式规则,Larger join unit heuristic。优化器尽可能地去扩展 join unit ,而不是用 binary join 去连接join unit 。
Search space enumeration
这部分主要介绍了如何进行全局优化,如何将 join unit 与SAP HANA查询优化器中的搜索空间枚举相结合,减少中间结果的生成。
HANA优化器基于Volcano/Cascade框架,采用DAG(有向无环图)对搜索空间进行表示。box内表示一个等价类,每个box内部是一组等价逻辑算子集合。
下图演示了执行以下关系代数的示例。γ 表示一个 group-by operator。
γ
(
(
T
1
⨝
T
2
)
⨝
(
T
3
⨝
T
4
)
)
γ((T1⨝T2)⨝(T3⨝T4))
γ((T1⨝T2)⨝(T3⨝T4))
下图为在搜索空间枚举过程中如何构造 m-ways join unit的伪代码, SAP HANA在搜索空间枚举遵循自底向上的原则。算法1给出了枚举搜索空间的主函数Enumerate(op)。遵循以下步骤:
input :a logical operator。
(1)从root operator 开始执行,检查op是否已经完成了物理枚举,如果完成了直接退出执行即可。(Line 1-2)
(2)如果没有完成,那么就要递归地进行op的child operator的搜索空间的枚举。(Line3-5)
(3)当所有child operator枚举完成后,进行op自身的逻辑枚举,标记为逻辑枚举已经完成(Line6-8)
(4)逻辑枚举完成后,进行op的物理枚举,详细步骤在算法2和3中。(Line 9)
(5)之后我们就可以处理下一个logical alternative。
算法2是op的物理枚举过程,需要对 m-way join unit 进行物理枚举,详细步骤在算法3中给出。
join unit的物理算子枚举算法就是根据结合的 operator 进行相应的操作。如算法3。
- 如果是Table,就对应Line 2-3中描述,将基表添加到 MJU的连接图中 。
- 如果是Join,先为 op 的子等价类选择成本最小的 MJU(注:这里需要用到下文提到的局部优化和成本估算),即GetMinCostMJU(),将两个join_graph 分别存储在j1和j2中,然后组合在一起合成新的连接图,存储在新的MJU中。需要注意的是j1和j2使用的 materialized 的临时表,以便于如果j1 or j2 包含临时表的情况
- 如果是group-by,选择op的子等价类中成本最小的MJU,将它添加到 join-graph 中(Line 11-12)。group-by的信息被存储在一个新的连接单元中,输出信息也存储在其中。如果算法4返回的是临时表,那说明需要创建一个新的MJU; 如果返回的是连接图,那么说明当前MJU可以继续扩展。
算法4 的函数如果组合了group-by 返回的是临时表,如果没有,那么返回的是连接图。
Local optimization and cost estimation of m-way join unit(局部优化和成本估算)
首先需要检验哪种算法能够应用于给定的m-ways join unit,如果能适用多种 join 算法,那么需要估算成本,选择最低成本的方法。
- m-way star join algorithm:只适用于star join and snowflake join等特定形状,需要确定哪个表是事实表
- m-way column join:需要确定半联接缩减的顺序。
The cost of the join unit = the m-way join cost + group-by cost + materialization cost.
Optimizer enhancements by exploiting m-way join unit characteristics(利用m-way join unit 的特性进行全局优化)
减少产生不必要的枚举
基本思想主要有以下两点:
- 如果 query 中只包含 join operator,且仅用列表保存,那么就可以跳过对每一对 join 进行枚举 ,可以直接映射到一整个unit join 算子;
- join order 可以在join unit 内部执行,不需要放到优化器全局来考虑。
基于以上两点,做出了以下改进,如算法5所示,当前等价类的逻辑枚举完成后,如果 op 中只包含 join 算子,那么对其子等价类标记为逻辑枚举已完成。
因此对于该逻辑表达式的改进如图所示。图中所有的灰色部分都被跳过。
γ
(
(
(
(
T
1
⨝
T
2
)
⨝
T
3
)
⨝
T
4
)
.
.
.
⨝
T
n
)
γ((((T1⨝T2)⨝T3)⨝T4)...⨝Tn)
γ((((T1⨝T2)⨝T3)⨝T4)...⨝Tn)
但是对于含有 group-by 算子的等价类,不能使用这种优化方案。因为 group-by 需要进行 pushdown 和 eager-aggregation ,binary join 的重排序仍然很重要。
以下为方法有效性的证明:
首先为该逻辑表达式定义了三个变量: Sn: 表示该逻辑表达式的搜索空间; An:表示等价类T1T2…Tn的逻辑执行计划的数量;Jn:表示Sn join 算子的数量
(
(
T
1
⨝
T
2
)⨝
.
.
.
)
⨝
T
n
((T1⨝T2)⨝...)⨝Tn
((T1⨝T2)⨝...)⨝Tn
引理1:在SAP HANA优化器方法中,尽管跳过了一些 binary join 枚举,但Sn包含了所有可能的等价类。
证明:
我们定义当且仅当An包含所有可能的逻辑执行方案的时候,Sn为 root-complete 。对于每个An的逻辑执行方案,左子项有 k 个关系(1≤k≤n−1),右子项有 n - k个关系。如果Sn是 root-complete ,那么 An = 2 ^ (n - 1) - 1;如果Sn是根完备的,则Sn包含所有可能的等价类。
本文采用归纳法证明了Sk对于任意k >= 2是根完备的:
- k = 2 时,T1和T2只有一个可能的方案,存在初始查询图中,所以Sn是 root-complete 。
- k > 2 时,如果Sk−1为根完全,则Sk为根完全。假设Sk−1是根完全的,那么Ak-1 = 2 ^ (k - 2) - 1;如果在Tk-1中新添加Tk,生成2*(2k−2−1)个新的逻辑执行方案和初始连接图中的一个逻辑方案,那么Ak = 2 * (2 ^ (k - 2) - 1) + 1 = 2 ^ (k - 1) - 1; 因此结论成立。
引理2:在SAP HANA优化器方法中,完全连接查询图的Jn = 2 ^ (n + 1) - 3n - 1。
证明:新生成的 join 算子的数量 = An + 新生成的等价类数量;
查询的形状以及相应搜索空间的大小不同,优化的效果可能也不相同,但是中间结果的生成都会因为跳过了笛卡尔积而减少。
减少产生重复的join unit 算子
不同的binary join 可能会产生相同的m-way join unit。为了避免为了避免生成冗余的 join unit ,本文引入了一个 join unit set 的新概念,由相应的等价类和所涉及的表列表组成。创建新的联接单元之前,将使用 join unit set 检查不必要的 join unit 创建。
减少局部优化
由于join order 需要从头开始执行,所以join unit 的成本估算非常昂贵。因此需要尽量避免使用成本估算。只有当join unit 中存在 group-by 算子时,才进行成本估算。否则,由于启发式规则Larger join unit heuristic,不需要基于成本的比较,并且可以跳过对相应联接单元的成本估计。
MIXED JOIN BETWEEN COLUMN TABLE AND ROWTABLE(行列混合连接)
SAP HANA支持行表和列表,还支持行表与列表之间的混合联接。下图是枚举了两个列表CT1、CT 2之间的联接,以及与行表RT1的联接。
- 当行表的大小相对小于列表时,RT1的数据 materialized 在一个临时列表中,并在其上动态构建一个字典。然后和CT1、CT2一起执行m-ways连接。
- 否则,将CT1、CT2的 连接结果 materialized 在一个临时行表中,执行hash join。