标题:工作负载驱动的、用于查询优化的数据依赖的延迟发现
***摘要***:有效的查询优化是关系数据库系统的支柱;查询优化器使用各种技巧,从缓存和批处理的专用辅助数据结构到辅助元数据的使用。然而,尽管存在许多基于依赖的查询优化技术,但这些系统并没有充分利用数据依赖的潜力,比如功能依赖。这种忽视之所以发生,是因为所需的数据依赖关系在实践中很难找到,也很难维护。
本文提出了一个工作负载驱动的、用于查询优化的惰性依赖关系发现系统。我们提出了一种轻量级方法,该方法根据实际执行的查询计划识别相关的数据依赖候选者,根据数据库动态验证候选者,并使用不同的策略维护结果,这些策略也利用了列式DBMSs的概念。我们的评估证明了这种方法的可行性和基于依赖的优化的潜力,使用Hyrise中的一个原型实现,它实现了三种典型的基于依赖的优化技术。例如,在自动发现依赖关系之后,这些优化将Join Order Benchmark的执行时间减少了27%。
用于优化的数据依赖性
元数据的利用是关系查询优化的一个重要支柱。我们提出了一个有效的应用程序,用于大量未开发的数据依赖关系的查询优化潜力,例如功能依赖关系或顺序依赖关系。通常,对于任何给定的数据集,由于数据中的自然相关性,但也由于模式非规范化和某些数据生成模式,许多不同类型的数据依赖关系[24]是有效的。此外,存在数十种基于数据依赖的查询优化技术[11],其中大多数已经存在多年。例如,数据依赖关系可用于获得改进的选择性估计,简化GROUP BY语句,或将昂贵的连接转换为本地谓词[11]。然而,数据依赖关系及其查询优化策略在很大程度上仍未得到充分利用,因为数据库系统没有意识到这些依赖关系。造成这种情况的原因是三个挑战,即依赖关系发现、选择和突变,这些挑战在数据库管理系统的上下文中实际上仍然没有得到解决。
考虑到discovery挑战,从手动或模式定义的约束(如主键或外键)派生的依赖关系仅占所有现有依赖关系的一小部分。有些依赖关系,例如顺序依赖,不能定义为约束,并且在某些用例中,不存在手动定义的约束和键。例如,许多科学数据集以CSV文件的形式提供,其中可能包含列标题,但不包含进一步的元数据。通过数据分析算法自动确定数据依赖关系是可行的,但成本昂贵,并且可能需要数小时的处理[1]。
然而,系统地分析依赖关系会导致第二个挑战,即依赖selection。由于数据分析算法会发现给定关系实例上所有技术上有效的依赖关系,因此结果集可能变得很大,而存储和有效访问它们也可能耗尽数据库的性能。例如,1m行的NCVoter数据集包含了大量的5m个最小FDs(以及许多其他类型的进一步依赖)[19]。如果数据库实际上可以存储所有发现的依赖关系,那么在查询优化期间可能仍然无法足够快地找到某个依赖关系,因为它们的检索不仅涉及依赖关系查找,还涉及推理:发现的依赖关系通常是最小的,但查询优化所需的依赖关系可能不是。目前还不存在从发现的依赖关系集中只选择与查询优化相关的依赖关系的策略。
假设存在这样的策略,mutation仍然是一个开放的问题,因为每个INSERT、UPDATE或DELETE语句都可以使昂贵的挖掘依赖关系无效,这使得它们对查询优化不安全;此外,可能会出现新的有用的依赖关系。因此,依赖关系发现、选择和突变,即整个元数据维护需要成为任何此类查询优化工作的一部分。
我们提出了一个集成的、自主的查询优化系统,该系统使用延迟发现的数据依赖关系进行查询重写和计划优化。根据观察到的工作负载,系统只按需发现支持某些优化的数据依赖关系,因此,这些数据依赖关系与数据库系统相关。它还通过动态添加新的依赖关系和随着时间的推移重新评估现有的依赖关系来增量地维护发现的依赖关系。通过这种方式,我们的系统解决了所有三个挑战:将discovery集成到优化过程中,并以这种方式验证更少和更合适的(即非最小)依赖关系;依赖关系的selection是工作负载驱动的,因此,既有效,又能很好地适应实际需求;由于与数据库操作的紧密集成和利用,有效的mutation是可能的。
综上所述,本文做出了以下贡献:
-
一个工作负载驱动的惰性发现系统,为查询优化器提供相关的数据依赖关系。
-
一组增量依赖关系维护技术,用于保持查询优化器的元数据有效。
-
将提出的发现系统与三种基于数据依赖的查询优化技术实际集成到开源DBMS Hyrise中[6]。
-
展示系统性能的评估(以及基于依赖的查询优化):在连接顺序基准测试中,总执行时间提高了27%,某些TPC-DS查询的速度提高了60倍以上。
背景
在本节中,我们将提供有关数据依赖关系、基于依赖关系的查询优化技术和数据库系统Hyrise的必要背景信息。
2.1 Data dependencies
数据依赖关系是定义良好的关系属性。它们通常表示一个表内的属性之间的关系,但一些依赖类型也可以跨多个表。现在我们简要介绍本文中用于查询优化的三种数据依赖关系。欲知详情,请感兴趣的读者参考[1]。以下定义主要采用文献[11]:
-
Unique column combinations (UCCs):如果X上的投影不包含任何重复元组,则属性集X形成唯一的列组合。更正式,给定关系实例𝑟在关系𝑅上,𝑋⊆𝑅是独一无二的,也就是说,对R的一个列组合(UCC),如果∀
,
∈𝑅,𝑖≠𝑗,有
[𝑋]≠
[𝑋]。UCC的一个流行示例是所有关系数据库键,例如订单表中合成id列或customer_id和时间戳的组合。
-
Functional dependencies (FDs):根据功能依赖𝑋→𝑌,所有在𝑋值上一致的元组也在𝑌值上一致。更正式,关系𝑅的函数依赖(FD)𝑋→𝑌保存关系实例𝑟在关系𝑅上,如果∀𝑠,𝑡∈𝑟,有𝑠[𝑋]=𝑡[𝑋]⇒𝑠[𝑌]=𝑡[𝑌]。例如,属性zip、street_name、latitude通常在功能上确定地址表中的city。
-
Order dependencies (ODs):顺序依赖𝑿↦𝒀表示按𝑿对表的元组排序也按𝒀对记录排序。在正式的术语中,关系R的两个列表的属性𝑿和𝒀,顺序依赖(OD)𝑿↦𝒀保存关系实例𝑟在关系𝑅上,如果∀𝑠,𝑡∈𝑟,有𝑠[𝑿]⪯𝑡[𝑿]⇒𝑠[𝒀]⪯𝑡[𝒀]。注意,比较运算符⪯按属性比较𝑿和𝒀值,即按字典顺序通过⪯比较,每个列表中的第一个属性是最重要的属性。employees表中的一个示例OD可能是salary↦taxrate。
2.2 Dependency-based query optimizations
自从创建关系理论以来,数据库社区开发了大量基于各种数据依赖关系的查询优化技术。最近的一项科学研究[11]描述并分类了59种优化,这些优化在实践中广泛未被充分利用。因此,在本研究中,我们举例选择并实施了这些技术中的三种;第4节展示了它们在查询优化方面的潜力:
-
O-1 Join to semijoin [16]:连接列上的UCC允许简化连接执行。考虑下面的查询:SELECT r.A, r.B FROM r, s WHERE r.ID = s.ID
如果ID是s上的UCC,上面的查询可以通过更有效的半连接策略执行。这种优化之所以有效,是因为UCC保证对于r的任何元组,只会有一个来自s的匹配元组。 -
O-2 Reduce GROUP BY attributes [3, 5]:通过减少分组属性的数量,可以使用功能依赖来更有效地执行分组操作。考虑语句GROUP BY X, A和FD 𝑋→A。根据FD 的定义,在X上一致的元组也将在A上一致。因此,在分组时,这些元组将属于相同的组,因此计算GROUP BY X是等价的。
-
O-3 Join avoidance [26]:在某些情况下,可以通过了解顺序依赖关系或UCCs来避免连接。考虑下面的简化查询摘要和一个OD
date:
SELECT ... FROM fact, dim WHERE fact.date_sk = dim.date_sk AND dim.date BETWEEN '20210816' AND '20210820'
如果最终结果不需要dimension表的列,则可以用本地谓词替换fact表和dimension表之间的连接:fact.date_sk BETWEEN min_date_sk AND max_date_sk where min_date_sk (and max_date_sk accordingly) 被计算为:
MIN(date_sk) min_date_sk FROM dim WHERE date >= '20210816'
dimension表通常比fact表小[9]。因此,min_date_sk和max_date_sk可以快速确定。产生的BETWEEN谓词通常比原始连接更有效。如果没有前面提到的OD,就不能保证如MIN(date_sk)决定正确的值。
与基于OD 的优化类似,如果dimension表在UCC上进行过滤,则可以将连接转换为低成本的本地谓词。不需要BETWEEN谓词比较连接列的最小值和最大值,一个简单的等于谓词就足够了。这些OD- and UCC-based join避免优化的应用,特别是它们在查询优化阶段的知识,可以实现另一种下游优化:在fact表上引入本地谓词,从而实现修剪机会,这在实践中通常会显示出显著的性能影响。这些影响包括在报告的O-3执行性能数字中。
2.3 Hyrise
为了演示我们基于依赖的查询优化系统,我们在DBMS Hyrise中实现了[10]它。在下面,我们将简要介绍Hyrise的架构和相关组件[6],因为它的一些概念对我们的方法是有益的。
Hyrise是一个主内存、面向列的数据库系统,具有隐式水平分区的存储布局。这些分区被称为块,并且有一个固定的最大大小(默认值:65535 tuples)。默认情况下,字典编码应用于所有块。此外,Hyrise采用只插入的方法,利用多版本并发控制(MVCC),其中删除和更新不会物理地删除行。相反,将行标记为不可见,并将新版本附加到表的最后一个块中。此外,Hyrise还提供了一个所谓的插件接口,是可以实现访问内部资源的自主组件。这样的组件可以读写内部数据结构,而不必与数据库的内核紧密耦合(参见[6, p.321]),这简化了我们方法的实现。
数据依赖的自主发现、选择和突变
在本节中,我们首先解释工作负载驱动的惰性数据依赖关系发现系统,该系统确定并验证特定查询优化目标的数据依赖候选项,只考虑与观察到的工作负载实际相关的依赖关系(第3.1节)。然后我们解释如何处理可能使依赖关系失效的数据更改(第3.2节)。虽然我们是为Hyrise实现了基于数据依赖的查询优化系统,但所提出的概念是基于通用数据库概念的,并没有在技术上与这个特定的数据库系统耦合;因此,它们也应该适用于其他数据库系统。
3.1 Workload-driven, lazy data dependency discovery and selection
数据依赖关系通常是未知的,在不限制搜索空间的情况下确定表或数据集上的所有依赖关系是非常昂贵的。我们方法的总体目标是有效地提供可用于查询优化的数据依赖关系。由于这个原因,并且与最先进的数据分析算法相比,我们通过不考虑所有可能的属性组合作为潜在的数据依赖候选者,而只考虑特别有希望的属性组合来解决这个问题。
为此,我们的方法包括两个阶段:首先,我们确定哪些数据依赖候选项是相关的,也就是说,在查询优化期间应用哪些依赖关系可能有利于处理系统的工作负载。其次,验证先前确定的候选项。
确定和验证相关依赖候选项的具体过程如图1所示。该图包含两个主要组件:(i) 一个能够应用基于依赖的优化的数据库系统(在我们的例子中是Hyrise);(ii) 依赖关系发现插件。
-
Database System:① 所有查询都以SQL字符串的形式传递给数据库系统进行处理。② 然后将SQL字符串转换为逻辑查询计划,进行优化,最后转换为物理查询计划。如果之前已经处理过查询,则可以从计划缓存中检索其计划。②.A 避免不必要的重新翻译和优化。此外,在优化期间,可能会从数据依赖关系存储中检索依赖关系。②.B 创建更高效的查询计划——这个扩展支持基于数据依赖的优化应用,比如2.2节中讨论的三个优化。③ 之后,执行查询计划,并与运行时间和基数信息一起存储在查询计划缓存中。
-
Dependency Discovery:实际的依赖发现扩展是作为Hyrise插件实现的[10](参见第2.3节),它以可配置的频率定期执行。④ 这个过程的基础是一组用户定义的规则,这些规则提供了从计划缓存中的查询,从执行的物理算子及其相应的逻辑算子中派生特定依赖候选项的逻辑。这些规则订阅特定的算子类型,例如,扫描或连接,并定义在什么情况下创建哪些依赖候选项。例如,对于O-3:如果两个关系连接在一起,其中一个关系没有为最终结果贡献属性,且该关系被过滤,然后创建一个OD候选项 join_column↦filter_column。换句话说,规则决定需要存在哪些依赖关系来实现某些优化(图1中的O-1、O-2、O-n)。注意,依赖关系选择是否促进有用的依赖关系取决于用户定义的规则。
⑤ 依赖发现过程访问数据库系统的计划缓存,该缓存作为已处理工作负载的表示。PlanParser以一个算子接一个算子的方式处理缓存的条目,比如物理和逻辑查询计划。
⑥ 将算子传递给所有规则,这些规则订阅特定算子类型(图1中的O-2和O-n)。⑦ 之后,规则(该体系结构中的活动组件)检查用户定义的需求,并返回适用的、特定于算子的依赖候选项(例如,图1中的OD和UCC候选项)。
⑧ 接下来,依赖关系验证器根据底层数据验证依赖候选项。在我们的案例中,在Hyrise中实现了该过程,验证过程受益于内存驻留数据、其面向列的存储布局和字典编码,有关更多详细信息,请参阅第3.2节。对于某些依赖类型,依赖验证器部分地使用Hyrise的高效算子实现,例如,用于验证顺序依赖的Sort算子。我们还使用现有验证算法的技术,如抽样[19],以快速消除候选项。在更通用的设置中,可以使用传统的SQL- or PLI-based 验证算法[1]。
⑧.A 验证之后,可用的数据依赖关系存储在数据库系统的数据依赖关系存储中,以便在查询优化期间使用。⑧.B 此外,出于效率原因,系统还保留了未成功验证的依赖候选项的列表。在验证期间访问这两个存储,以避免对未更改的底层数据重复不必要的验证。⑧.C 此外,从计划缓存中删除相应的查询计划,以启用重新优化,从而使用经过验证的依赖关系进行优化。
3.2 Efficient data dependency mutation
例如,通过在某些列中引入重复项,数据更改可能会使依赖关系失效。然后,在查询优化中使用无效的依赖关系会导致成本和基数估计不佳、查询计划效率低下,在最坏的情况下,还会导致错误的结果。虽然这种错误在某些情况下是可以接受的,比如近似查询处理[14,18],但它们通常是不可容忍的。
在本节中,我们将介绍使依赖关系保持更新的三种技术:(i) 工作负载驱动的发现,(ii) 增量验证和维护 (iii) 利用列存储DBMS概念。现在我们更详细地讨论每种技术:
-
Workload-driven discovery:关于数据依赖关系的一般观察是,虚假的依赖关系可能会经常更改,而真正的依赖关系(如语义上有意义的依赖关系)建模一个真实约束,在数据集的生命周期中不会或很少更改。因为我们的系统从真实的、已执行的、可能是人工制作的SQL查询和数据集中提取依赖关系,所以发现的依赖关系中有很大一部分应该是真实的。因此,工作负载驱动的发现可以作为语义预过滤器,与其他挖掘的依赖关系集合相比,可以显著减少依赖关系存储中的更改量。除了这一观察之外,我们的评估(参见第4节)还揭示了维度表上存在许多有益的依赖关系,例如日期表或类型表,这些表很少更新[9,第141页];如果确实需要更新,由于维度表的大小相对较小,验证依赖关系的成本很低[9],正如我们在4.3节中观察到的那样。
-
Incremental validation and maintenance:如果我们的基于依赖关系的查询优化系统应用于某些数据仓库场景,其中数据以特定的、低频率的周期更新[25],那么在批处理作业中保持依赖关系最新是很容易的。但是,对于更一般的设置,我们需要关注更细粒度的更新:给定具体的数据更改,没有必要重新评估所有依赖关系,也没有必要完全重新评估依赖关系。相反,我们建议使用FDs[4,23]和ODs[27]中存在的高效增量验证和维护方法。对于UCC维护,我们可以采用最初设计用于执行键约束的技术[15]。这些方法使用巧妙的技术和索引结构,选择性地重新评估仅受影响的用例。因为我们的系统只维护依赖关系的一个非常小的子集,并且不会用新的最小依赖关系逐步替换无效的依赖关系(正如引用的增量发现系统所做的那样),因此维护效率要高得多。
-
Utilization of column-store DBMS concepts:除了前面讨论的增量验证技术之外,某些列存储概念对于处理数据更改特别有用,因为它们可以有效地进行依赖关系验证和处理可能无效的依赖关系。现代的列存储系统,如DuckDB[22]、HyPer[8]或Hyrise[6],通常以块的形式存储值,这些块是固定大小的隐式水平分区,当容量达到最大时被压缩并不可变[12]。UPDATE语句被实现为仅追加操作,在原始元组失效之后,插入一个包含更新值的新元组。至少可以通过两种方式利用这种布局:首先,与某些数据库操作类似,一些依赖关系验证算法直接受益于列式存储布局。由于关系数据依赖关系表示属性(即列)之间的关系,因此通常只需要读取很少的列进行验证。由于在列式存储布局中可以精确地访问相关数据,列式存储(如Hyrise)可以更有效地(重新)验证依赖关系。列存储的第二个优点是,此类系统通常依赖于压缩技术,例如字典编码,这可以进一步改进验证,特别是对于增量测试。例如,我们的实现使用Hyrise的块字典在验证UCC候选项期间快速确定非唯一列组合。
总而言之,数据更改带来的挑战在很大程度上可以通过我们的工作负载驱动架构、最新的增量维护技术和现代列存储概念来解决。
评估
为了评估我们的工作负载驱动的依赖关系发现系统用于查询优化以及基于依赖关系的查询优化技术本身,我们将该系统实现为Hyrise插件[10],该插件分析Hyrise的计划缓存以确定和验证有用的依赖候选项。我们还将优化的O-3集成到Hyrise中,而O-1和O-2已经在以前的版本中集成了。
4.1 Experimental setup
-
CPU:主存为384 GB的Intel Xeon 8180 Platinum
-
Datasets and workloads:TPC-H[21]、TPC-DS2[17]和Join Order benchmark (JOB)[13]。前两种方法使用合成数据集,是专门为基准分析系统设计的,而后一种方法使用来自IMDB的真实数据。
为了模拟第1节中描述的场景,其中系统必须自己识别所有依赖候选项,没有为基准定义(外)键或约束。对于单个基准的评估,我们首先将基准执行时间作为其整个工作负载超过100次执行的平均(平均数)执行时间进行度量。然后,我们调用依赖关系发现插件,它实际上会在查询处理期间在后台运行。之后,执行相同的工作负载并再次进行测量。通过这种方式,实验分别测量了插件的执行时间和完全优化的工作负载。所有实验都以单线程方式执行。
4.2 Optimization performance
表1显示了三种数据依赖关系驱动的查询优化技术的性能影响(执行时间)以及整个依赖关系选择和发现过程(候选项)的信息。除了对执行时间的影响之外,表1还显示了有多少查询的性能得到了提高或降低。对于选择和发现过程,描述了确定的和有效的候选插件的数量以及依赖关系发现插件的总运行时间。
作为第一个观察,实现的好处是巨大的,对于JOB和TPC-DS尤其如此,这三种优化的组合分别将执行时间减少了27%和10%。除了表中包含的汇总统计信息外,我们还想指出,每种优化都会对某些查询产生重大改进。此外,该表还显示了在某些情况下,这些优化会相互竞争。
Discussion:基于依赖的查询优化技术对性能的影响程度取决于数据模型、查询、评估的优化和底层数据。例如,O-1和O-3显式地以连接为目标,连接在JOB中频繁出现,并且在JOB和TPC-DS中具有比聚合更大的总体影响,而聚合是O-2的目标。此外,TPC-DS和JOB的类似星型模式的数据模型更适合这种优化。最后,数据本身决定依赖关系是否存在:与TPC-H(35%)和-DS(19%)相比,基于真实世界数据的JOB的有效依赖关系和候选依赖关系的比例很高(85%)。
4.3 Discovery and selection overhead
使用依赖关系进行查询优化会在实现的性能改进和确定依赖关系的开销之间进行权衡。因此,虽然在某些情况下,由初始依赖关系发现和选择过程引入的开销是可见的,但必须在考虑实现的性能增益的情况下对其进行判断。表1的Time列显示了确定依赖候选项及其验证所需的时间。
验证运行时间的差异有多种原因。首先,某些类型的依赖候选项比其他类型的依赖候选项更难验证或排除。例如,确定或排除唯一性通常比对具有数千万行的大型表进行排序更简单。与候选验证相比,候选生成(即查询计划分析和候选提取)的开销要低得多。对于所提出的实验,候选生成所消耗的依赖关系发现时间从不超过总时间的10%。最后,表的大小和数据的性质会影响验证成本。事实上,验证TPC-H的客户表c_address上的单个UCC(约150万行)需要1秒,并且占发现和选择运行时间的36%。相比之下,TPC-DS从一个小尺寸表上的OD中获利,该表只需41毫秒即可验证。最后一个观察结果与第3.2节中提出的论点一致:有价值的数据依赖关系导致性能改进,可以快速(重新)验证。
Discussion:上面的实验表明,在大多数情况下,依赖关系发现的工作量通常很快就会摊平。更重要的是,发现过程与查询执行和优化无关。因此,它可以作为异步后台任务执行,其运行时间的相关性大大降低。此外,对较大的TPC-H和TPC-DS数据集的初步评估表明,性能收益的增长速度至少与验证工作的增长速度一样快。
相关工作
在下文中,我们将简要讨论研究如何将数据依赖关系用于查询优化的现有方法。
在商业数据库系统中使用了一些基于依赖的查询优化技术。然而,这样的系统不能自主地确定必要的依赖关系。因此,这些优化是基于用户定义的依赖关系。示例包括UCCs和INDs(参考完整性约束),用于减少统计视图的数量[7],用于避免连接的ODs[26],或用于改进选择性估计的FDs(仅在用户指示下进行验证)[28]。
Pena等人[20]提出了一种自动合并FDs进行查询重写的系统。首先,他们的方法需要发现所有的功能依赖关系。然后,通过比较 (i) 表示FDs的属性矩阵 (ii) 工作负载查询中属性发现的矩阵来确定它们的适用性。通过重写来优化查询的FDs是根据质量指标或聚类的排名来选择的。与我们的方法相反,它们的依赖关系发现过程并不局限于相关的候选项,这可能导致较高的计算成本。此外,只执行查询重写。虽然这种方法可以在不调整数据库系统的情况下工作(重写只能在SQL字符串上执行),但并不是所有的优化都可以通过重写来实现,并且无法实现计划级别优化的全部潜力。此外,我们的评估表明,其他依赖类型比FDs表现出更大的性能影响。
总结
我们提出了一种方法,可以根据具体的、给定的工作负载有效地确定数据依赖关系,并在查询优化期间应用它们来生成更有效的查询计划,从而提高性能。在应用数据依赖关系进行查询优化时,面临的三个挑战是发现、选择和改变相关依赖关系。我们提出了一个集成的解决方案,通过我们的工作负载驱动、惰性依赖关系发现方法、增量验证和维护技术以及列存储DBMS的概念来解决这些挑战。
使用开源DBMS Hyrise进行的评估显示了分析基准TPC-H、TPC-DS和JOB的良好结果:观察到的运行时间改进是实质性的(工作负载执行时间减少了27%,查询速度提高了60倍以上),确定和验证依赖候选项的开销是合理的:在观察到的执行时间减少在0.05到1.3倍之间。但是请注意,发现工作是作为异步后台任务运行的,因此不需要考虑立即影响查询优化成本的因素。此外,在进行的实验中,我们观察到那些对查询优化有益的依赖关系也可以快速地重新验证,从而减轻了依赖关系突变的影响。