基于深度强化学习的连接查询优化

Krishnan, S., et al. (2018). "Learning to optimize join queries with deep reinforcement learning."

如何优化 SQL 连接是数据库社区数十年来一直在研究的一个大问题。伯克利 RiseLab 公布的一项研究表明,深度强化学习可以被成功地应用在优化 SQL 连接上。

这篇论文表明了如何通过深度强化学习技术来攻克这个已经存在了数十年的挑战。作者将连接排序问题表示为马尔可夫决策过程(MDP),然后构建了一个使用深度 Q 网络(DQN)的优化器,用来有效地对连接进行排序。然后基于 Join Order Benchmark(最近提出的工作负载,专门用于压力测试连接优化)对本文的方法进行了评估。

 

背景

数据库社区已经针对 SQL 查询优化问题进行了近 40 年的研究,可以追溯到 System R 的动态规划方法。查询优化的核心是连接排序问题。尽管这个问题由来已久,但仍然有很多研究项目尝试更好地理解多连接查询中的连接优化器的性能,或者解决在企业级“数据湖泊”中无处不在的大型连接查询问题。

 

RiseLab实验室

 

UC Berkeley大学的AMPLab曾是大数据领域世界顶尖的实验室之一,六年来推出了多项主要的科技创新技术,比如Apache SparkApache MesosAlluxio,而如今它已经关闭,取而代之的是RISELab实验室。RISELab实验室会专注于提供SRDS,即安全实时的决策堆栈。现有比较知名的工作有Ray、Drizzle等。

传统动态规划方法

 

我们先回顾一下传统的动态规划(DP)方法。

假设一个数据库包含三张表:Employees、Salaries 和 Taxes。下面这个查询用于找出“所有 Manager 1 员工的总税额”:

SELECT SUM(S.salary * T.rate)

FROM Employees as E, Salaries as S, Taxes as T

WHERE E.position = S.position AND

                T.country = S.country AND

                E.position = ‘Manager 1’

这个查询包含了三个关系连接。在下面的示例中,我们使用 J(R) 表示访问基本关系 R 的成本,使用 J(T1,T2) 表示连接 T1 和 T2 的成本。为简单起见,我们假设了物理执行层只有一个访问方法、一个连接方法和一个对称连接成本(即 J(T1,T2)=J(T2,T1))。

简单介绍下左深树、右深树以及稠密树连接

Left-Deep:所有右子树必须是一个关系,左子树则没有要求

Right-Deep: 所有左子树必须是一个关系,右子树则没有要求

Bushy:除了LD、RD都算Bushy

Zig-Zag(VLDB’93):zig-zag的特点是子树中至少有一个是关系,因此它既包含左深子树也包含有右深子树。

 

经典的“left-deep”DP 方法首先计算访问三个基本关系的最佳成本,我们把结果放在一张表中:

Remaining Relations

Joined Relations

Best

{E, S}

{T}

J(T), i.e., scan cost of T

{E, T}

{S}

J(S)

{T, S}

{E}

J(E)

然后,它基于这些信息枚举所有二元关系。例如,在计算{E,S}连接的最佳成本时,它会查找先前相关的计算结果:

Best({E, S}) = Best({E}) + Best({S}) + J({E}, S)

这样就得到了新的一行:

Remaining Relations

Joined Relations

Best

{E, S}

{T}

J(T), i.e., scan cost of T

{E, T}

{S}

J(S)

{T, S}

{E}

J(E)

{T}

{E, S}

Best({E}) + Best({S}) + J({E}, S)

这个算法遍历其他二元关系集,最终找到连接所有三张表的最佳成本。这需要在二元关系和基本关系的所有可能“left-deep”组合中取最小值:

Remaining Relations

Joined Relations

Best

{E, S}

{T}

J(T), i.e., scan cost of T

{E, T}

{S}

J(S)

{T, S}

{E}

J(E)

{T}

{E, S}

Best({E}) + Best({S}) + J({E}, S)

{E}

{T, S}

Best({T}) + Best({S}) + J({T}, S)

{S}

{E, T}

Best({E}) + Best({T}) + J({E},T)

{}

{E, S, T}

minimum { Best({E,T}) + J(S) + J({E,T}, S),
Best({E,S}) + J(T) + J({E,S}, T),
Best({T,S}) + J(E) + J({T,S}, E) }

这就是动态规划方法。假设有N个关系,这个算法的空间复杂度和时间复杂度为O( N! ),这就是为什么它通常只被用于 10 个以内关系的连接查询。

现有方法不足

连接顺序选择问题是一个NP-hard问题,现有很多启发式的方法来帮助高效的寻找较优的连接顺序。这些启发式方法在线性代价模型下(join代价与它输入关系的大小是成线性关系的)是易于理解的,然而很多现实系统中,存在大量的非线性代价模型,例如中间结果超出内存大小从而导致溢写磁盘、或者关系大小超过某个阈值导致底层的物理执行实现更改(如使用Sort Merge Join 还是 Hash Join)等。因此,很容易找到经典的启发式方法失效的情况,如下图所示,左侧的Index-Mostly表示所有数据基本都在内存中,这个时候左深树搜素的方法表现尚可,但是当场景稍微复杂点,如右边两个,中间那个是混合Hash场景,数据会有部分溢写到磁盘中,右边是Hash表重用的场景,在这两个场景下,左深树搜索方法均表现较差。

 

 

文中涉及的其它方法:

QuickPick(British National Conference on Databases’2000):它通过随机的生成1000个可行的连接计划,然后利用估计器进行评估得到这1000中最好的计划作为输出。QuickPick-1000能够很快的给出一个连接计划,同时由于它没有对搜索空间进行限制,可能给出很差的计划。

IK-KBZ(VLDB’86):它将Left-Deep Tree 当作一个序列,以多项式时间找到最优计划,它假设的代价模型比较简单(代价函数需要满足相邻序列交换属性Adjacent Sequence Interchange (ASI)),通过这样的代价模型计算出一个rank值,每次选择关系进行join时都会计算一遍所有剩余关系的rank值,然后贪心选择rank最小的。它比较适合无环查询图,对于有环查询图解质量较差。直觉上就是尽可能让前面的join产生的中间结果最小。

 

方法

 

通过强化学习进行连接排序

将连接排序问题表示成MDP

  1. 查询图G=(V, E):其中vi表示顶点即关系,ei表示一条边,即关系之间的连接条件。状态
  2. 连接c=vi,vj:表示两个关系进行join                                      动作
  3. 下一个查询图G’:应用连接c后,产生的新查询图(包含连接的中间结果)       下一个状态
  4. Jc:连接c的代价                                                        奖励

连接排序可以简单的表示成(G, c, G’, J),整个排序过程,就是不断从查询图中选取两个关系进行join,直到只剩下一个关系。

先考虑贪心算法,假设有三个关系E, P, S, 有以下查询

 

 

各个关系的连接代价如上所示,贪心算法每一步都会考虑当前最优,因此会选择SPE 这样的连接顺序,总代价就是140,而最优的连接顺序是EPS ,代价是110。该算法时间复杂度为O(N^3),在时间效率上明显优于dp方法,但是得到的解往往比较差。

强化学习类似贪心算法,不同的是,贪心算法考虑的是当前代价最小的选择,而强化学习则考虑的是长期回报代价最小的选择,即每次选择,都选择对整个排序过程来说长期收益最大的那个。在上面的例子中,强化学习会知道,一开始选择EP的长期代价最小,因此可以得到最优解。其难点就是要训练一个函数,能够评估当前每一个选择的长期代价。在Q-learning(一种流行的 RL 技术)中,我们称这个函数为Q函数Q(G, c),它直观地描述了每个连接的长期成本:我们在当前连接决策之后对所有后续连接采取最佳操作的累积成本。

Q(G,c)=J(c)+\min{_c’}Q(G’,c’)

 

如果我们可以访问真正的 Q 函数,就可以进行贪心式的连接排序:

算法 1

  1. 从初始查询图开始;
  2. 找到 Q(G,c) 最低的连接;
  3. 更新查询图并重复。

根据贝尔曼的最优性原则,我们的这个算法可证明是最优的。这个算法令人着迷的地方在于它的计算复杂度为 O(n^3),虽然仍然很高,但却远低于动态规划的指数级运行时复杂度。

 

图 1:使用神经网络逼近 Q 函数。输出的意思是“如果我们在当前查询图 G 上进行连接 c,那么最小化长期连接计划成本是多少?”

当然,实际上我们无法访问真正的 Q 函数,需要对其进行近似。为此,我们使用神经网络(NN)来学习 Q 函数,并称其为深度 Q 网络(DQN)。这项技术与用于学习 Go游戏专家级玩家能力的技术AlphaGo如出一辙。总而言之,我们的目标是训练一个神经网络,它接收 (G,c) 作为输入,并输出估算的 Q(G,c),见图 1。

深度强化学习优化器 DQ

现在介绍我们的深度强化学习优化器 DQ。

 

数据采集​​

要学习 Q 函数,我们首先需要观察过去的执行数据。DQ 可以接受来自任何底层优化器的一系列 (G, c, G’, J)。例如,我们可以运行经典的 left-deep 动态规划(如背景部分所示),并从 DP 表中计算出一系列“连接轨迹”。完整轨迹中的元组看起来像是 (G,c,G’,J)=({E,S,T}, join(S,T), {E,ST},110),它代表从初始查询图(状态)开始并将 S 和 T 连接在一起(动作)的步骤。

我们已经使用 J 表示连接的估算成本,但如果数据时从真实的数据库执行收集而来,我们也可以使用实际的运行时。

当连接关系的数量不超过 10 个时,DQ 用bushy dp收集训练数据,否则用贪心算法收集数据。

 

 

状态和动作的特征化

由于使用神经网络来表示 Q(G,c),我们需要将状态 G 和动作 c 作为固定长度的特征向量馈送到网络中。DQ 的特征化方案非常简单:我们使用 1-hot 向量来编码(1)查询图中存在的所有属性的集合,包括模式中的所有属性,(2)连接左侧的参与属性, (3)连接右侧的属性。如图 2 所示。

 

图 2:查询及其相应的特征化。我们假设一个包含 Employees、Positions 和 Salaries 三张表的数据库。图中显示了部分连接和完全连接。(G,c) 的最终特征向量是 A_G(查询图的属性)、A_L(左侧的属性)和 A_R(右侧的属性)的串联。

虽然这个方案非常简单,但它具有足够的表现力。需要注意的是,我们的方案(和学习的网络)假设的是一个固定的数据库,因为它需要知道确切的属性集和表集。

 

上图(a)是在查询图特征中融入谓词的选择率,图(b)是在join特征中加入物理算子特征。

 

神经网络训练和规划

默认情况下,DQ 使用简单的两层全连接网络,并使用标准随机梯度下降进行训练。在完成训练后,DQ 可以接受纯文本的 SQL 查询语句,将其解析为抽象语法树,对树进行特征化,并在每次候选连接获得评分时调用神经网络(也就是在算法 1 的步骤 2 中调用神经网络)。最后,可以使用来自实际执行的反馈定期重新调整 DQ。

算法 1

  1. 从初始查询图开始;
  2. 找到 Q(G,c) 最低的连接;
  3. 更新查询图并重复。

 

结果

为了评估,本文使用了Join Order Benchmark。这个数据库由来自 IMDB 的 21 个表组成,并提供了 33 个查询模板和 113 个查询。查询中的连接关系大小范围为 5 到 15 个。

实验结果主要回答以下这三个问题:

DQ在制定计划时效率如何?有多好?在什么条件下?

DQ在运行计划和所需数据方面的制定计划效率如何?

DQ的技术是否适用于实际场景,系统和工作负载?

为了解决前两个问题,我们在独立的DQ上进行了实验。 最后一个问题是通过DQ集成的Postgres和SparkSQL的端到端实验进行评估的。

 

:1)上图是不同成本模型的平均计划次优性对比。2)CostModel1:模拟内存数据库并鼓励使用索引连接(也存在Hash连接),CostModel2仅考虑具有内存预算的Hash连接和嵌套循环连接。CostModel3考虑重用已构建的散列表。3)实验进行了 4 轮交叉验证,确保仅对未出现在训练工作负载中的查询进行 DQ 评估(对于每种情况,我们在 80 个查询上训练并测试其中的 33 个)。我们计算查询的平均次优性,即“成本(算法计划)/ 成本(最佳计划)”,这个数字越低越好。

结论:1)在所有成本模型中,DQ 在没有指数级别的先验知识的前提下可以接近最优方案。对于固定的动态规划,情况并非如此:例如,left-deep 在 CM1 中产生了良好的计划,但在 CM2 和 CM3 中效果没有那么好。同样,right-deep 计划在 CM1 中没有竞争力,但如果使用 CM2 或 CM3,right-deep 计划突然变得不那么糟糕。2)DQ表现得更加可以适应工作负载、数据或成本模型的变化。

 

:上图是所有 113 个 JOB 查询的优化器延迟,按查询中的关系数量进行分组。误差棒表示平均值附近的±标准偏差。共进行了 5 次试验。

结论:1)在大型连接中,DQ实现了极大的加速,对于最大的连接,DQ 的速度是穷举的 10000 倍,比 zig-zag 快 1000 倍,比 left-deep 和 right-deep 枚举快 10 倍。2)如果采用GPU或者TPU加速,它会表现更大的优势

 

注:上图是随着训练查询个数的增加,DQ给出的执行计划的表现变化。虚线是QuickPick算法的表现。

结论:当训练查询个数超过30个时(基本覆盖所有的关系),DQ相对QP更优。

 

实际上,DQ可以使用小型连接查询数据进行训练,然后在大型连接查询上进行测试。

 

注:1)上图是DQ在不同类型训练数据集下计划的次优性(Mean Relative Cost 越小越好)。2)测试集最多有15关系的连接查询。

结论:DQ仅用不超过9个关系的连接查询数据进行训练,就能达到接近最优的效果,但是更小的连接查询数据则效果会明显下降,原因是数据集覆盖能力不够。

 

注:1)上图是随着训练数据集相关性变化,DQ的计划的次优性(越小越好)。2)所谓训练数据集相关性是指训练数据集与测试数据集的相关性。3)R80指80条训练数据的连接关系和连接谓词都是随机生成的;R80wp指80条训练数据的连接关系是随机生成的,但是连接谓词来自JOB的谓词;WK80是指80条数据就是JOB中采样的的80个查询;T80指训练集覆盖了JOB全部33个查询模板。从左到右,数据相关性越来越强。

结论:1)即使是随机生成的数据都有不错的效果,因此DQ可以不需要事先知道workload。2)随着数据相关性增加,DQ的表现也会更好,同时意味着数据相关性越强,DQ可以用更少的训练数据达到同样的效果。

 

注:1)上左图是DQ与Postgre的优化器进行对比的实验,包括运行时间与优化时延。2)实验中,采集数据时Postgre的优化器配置成了bushy dp,对比时Postgre依然是默认的Left-deep dp。3)右图是将DQ的方案限制在LD和EX(即左图)时,带来的提升。

结论:1)在运行时间方面,部分查询DQ速度明显优于Postgre,平均性能提升在14%左右。2)在优化时延方面,对于大型连接查询,DQ优化延迟比Postgre大概快3倍。3)即使将DQ方案空间限制在LD,也能带来执行时间的提升,原因可能是DQ消除了优化器评估误差的不一致性。

 

注:1)上图是DQ与Spark的优化器进行对比的实验,包括运行时间与优化时延,跑的是TPC-DS。2)实验中Spark优化器为bushy dp

结论:1)在运行时间方面,DQ不比Spark bushy dp差。2)在优化时延方面, 在右下角出的圆点,是一个18关系的join,DQ有明显的优势(250倍)。主要因为TPC-DS查询集中join关系数较少。

 

注:1)上图对比了Postgre与DQ的不同变种在查询Q10c上的表现。 2)DQ是指仅用Postgre的评估器产生的数据离线学习;DQ+FineTuned是指在DQ的基础上,使用真实数据在线学习;Online-DQ是指仅用真实数据在线学习。

结论:1)使用真实运行时间微调离线学习的模型会使得模型更加精准,将近100个实际运行时间数据微调时,DQ给出的执行计划运行时间比原来快3 倍,比Postgre快3.5倍,这说明该模型部署到实际场景中表现会越来越好。2)仅采用真实数据学习由于数据较少,收敛会很慢,因此图中表现不佳。

 

数据敏感性

 

注:1)上表显示的是QP与DQ两种方法对数据的敏感性(即训练数据集变化时,模型的稳定性)比较,越小越好。2)该实验中,使用5组不同的数据集训练5个DQ模型,对固定的20个查询进行测试。

结论:相对于完全随机的QP算法来说,DQ对数据的敏感性显得更低。

 

基数误差敏感性

 

注:1)上表是各算法对基数误差的敏感性比较(由于数据库优化器的基数估计导致的误差),值越小越好。2)N是随机选取N个关系,对其基数乘上一个随机系数。3)对于DQ来说,会用带噪声的数据进行训练,然后用真实基数进行测试。而对于动态规划等方法来说,会直接假基数去做动态规划。4)表中的值是物理IO的次数取对数。

结论:可以看到,DQ不比KBZ对基数误差更加敏感。

 

消减研究

 

注:1)上表是在模型输入特征( fG+fc )中减去一些特征,如去掉整个fG 或者从 fG 中去掉选择率的信息对查询的影响。

结论:可以看到,没有这些特征会导致更大的Loss值,因此这些特征有助于提升DQ的表现。

 

结论

使用强化学习做连接顺序选择优化在大型连接查询场景下有非常大的优势,可以以更快的速度得到更好的查询计划,并且随着模型部署到实际应用中,模型本身的自适应优化能力也使得随着时间增长,获益越来越大。

但当前工作也存在一些不足:如本文仅考虑基于关系外键的内连接,还需要拓展到更一般的场景。

猜你喜欢

转载自blog.csdn.net/Fei20140908/article/details/109668569