Access Path Selection in Main-Memory Optimized Data Systems: Should I Scan or Should I Probe?


文章比较了内存数据库的顺序扫描和二级索引扫描,并通过建模分析和实验,讨论了在不同场景下两种扫描算法的优劣性。

1、简介

对于一个query请求,将逻辑扫描算子转换为物理扫描算子时,有以下几中情况:如果谓词所在的列上没有索引,则只能顺序扫描;如果谓词所在的列上有聚集索引,则索引扫描效率较高;如果谓词所在的列上是二级索引(更常见的情况),则需要和顺序扫描进行比较,可能性能比它高,也可能性能更低,取决于运行时环境。

文章对顺序扫描和基于B+树的二级索引扫描进行了建模分析,并证明了在系统中有一个临界条件,使得查询可以通过这个条件来判断选择哪种扫描算子。传统优化器选择物理扫描算子只考虑了选择率的影响,当一个查询的选择率大于某个阈值时选择顺序扫描,低于这个阈值时选择索引扫描。而文章证明了影响两种扫描方法代价的因素除了选择率还有查询并发数,如下图所示,随着选择率和查询并发的增大,顺序扫描的优势逐渐增大,二级索引扫描的优势逐渐减小,文章的目标就是找出这样一个倾斜的分割线。之后的查询只需要根据选择率和并发数查看其落在图中哪个区间,即可判断选择哪种扫描算法。

查询并发对扫描代价的影响主要在于对顺序扫描结果的共享,以及对二级索引树的遍历次数。当查询并发数增大时,对顺序扫描结果共享的优势将扩大,而对二级索引树遍历次数的增多将导致索引扫描的性能下降。

img

聚集索引:数据库表中数据的物理顺序与索引顺序相同。一个表只能有一个聚集索引,因为一个表的物理顺序只有一种情况。

二级索引:二级索引树的叶子节点存储的是主键而不是数据。在找到索引后,得到对应的主键,再回到一级索引中找主键对应的数据记录。

2、Cost Model

通过对两种扫描方法进行开销建模,可以计算出它们在不同场景下的代价如何。传统建模方法考虑的因素包括:选择率、硬件特性、数据分布等,本文除了上述因素外还考虑了查询并发数对代价估计的影响,具体参数如下图所示。

img

查询并发数对顺序扫描和索引扫描的影响如下图所示。当并发数为1时,顺序扫描只遍历一次元组,并根据谓词筛选出满足条件的元组。当并发数大于1时,顺序扫描在这里称为shard scan,即对多个查询只顺序扫描一次,并根据它们的谓词分别筛选出对应的元组,产生多个查询结果。而当并发数大于1时,每一次索引扫描都要遍历一遍索引树。

img

2.1 Shard Scan

Shard Scan是顺序扫描的扩展,因此下面先对顺序扫描进行建模分析,再在其基础上推导出Shard Scan的开销模型。顺序扫描可分为读取数据、谓词评估、结果写入三个部分,下面依次对它们进行分析说明。

2.1.1 Data Movement for Scan

根据读取内存带宽BWs,扫描的tuple数N,以及每个tuple的大小,可以计算出读取的开销TDs为:

img

对于列式存储,ts指单列或多列的大小,取决于数据的物理分布。

2.1.2 Predicate Evaluation

cpu对谓词进行评估的开销影响扫描的性能,其代价计算公式为:

img (为什么要乘2?原文说是计算谓词的上下边界)

其中,fp是用来评估指令调度流水线的常量,p是cpu时钟周期,N是tuple数,即需要计算的次数。

2.1.3 Result Writing

将结果写入内存的开销也是总开销的一个重要组成部分,其计算方法与读取数据的开销类似:

img

其中,BWr是内存写入带宽,rw是输出结果的大小(可能是单列或多列)。

至此,对于单个顺序扫描 i的代价计算公式就可以得到了:

img

其中,si是这个查询的选择率。考虑到CPU的多核多线程以及SIMD技术(Single Instruction Multiple Data),可以认为数据读取和谓词计算是同时进行的,所以在计算过程中,只取它们的最大值。

2.1.4 Scan Sharing

现在将模型扩展到q个查询共享一次顺序扫描,多个查询可以分担数据移动的成本,但是谓词评估的开销会随着查询个数的增多而增大。考虑到数据移动是扫描性能的主要瓶颈之一,所以扫描共享至关重要。 首先,要将谓词中属性相同的多个查询整合到一起(这里考虑的是不完整读出整个tuple的情况),然后分批从内存中读出cache能容纳的一小部分数据,使得多个查询可以共享这部分数据而避免数据被换出。

由于每一个查询都会产生一个结果集,所以将输出结果写入内存的开销为img,其中Stot是多个查询的总体选择率,Stot可能大于100%,比如三个选择率是40%的查询,他们的Stot为120%。

至此, shared scan的代价计算公式为:

img (q*PE考虑的是单线程计算多个查询的情况,多核多线程时性能应该更好)

 

2.2 Secondary B+-Trees

在B+树二级索引中,在叶子节点存储了被索引的属性以及所在元组的rowID,叶子节点以链表的形式保存,如下图所示。在内存数据库中,二级B+树索引扫描分为以下几步:1、搜索树找到满足谓词条件的第一个叶子节点;2、遍历叶子节点并读取rowID;3、将结果写入内存。此时结果可能不是按照rowID排序的,为了和顺序扫描的结果保持一致,还需要一步排序操作。下面,我们将对这些步骤进行建模分析。

B+树索引

2.2.1 Tree Traversal

首先,我们要找到第一个叶子节点,代价计算公式为:

img

其中,img为树的层高,也即找到叶子节点需要遍历的内部节点数,b是内部节点中元素的个数,Cm是随机读取内存地址的开销,img是在内部节点中找到对应元素的开销,这里认为平均需要遍历一半的元素,img是谓词评估时间。整个查找叶子节点的过程为,首先在树的根节点中遍历元素,找到满足谓词条件的元素,然后找到随机访问内存找到下一个内部节点,再遍历其内部元素,以此类推直到找到叶子节点。

2.2.2 Leaves Traversal

找到第一个叶子节点后,需要遍历该节点之后的叶子节点,找出满足谓词的所有叶子节点。由于叶子节点通常用链表连在一起,所以对每个叶子节点的访问都是随机内存访问。通过选择率Si,我们可以计算出大概要遍历多少个叶子节点,因为每个节点保存b个元素,所以一共有N/b个叶子节点。遍历叶子节点的开销为:

img

2.2.3 Data Traversal for Secondary Indexes

在每一个叶子节点中,查找属性值和rowID是顺序遍历,所以开销就是需要遍历的数据的总量除以带宽:

img

其中,一共要遍历Si * N个元素,每个元素大小为aw+ow,aw是索引字段的size,ow是rowID的size。

2.2.4 Result Writing

与顺序扫描类似,将结果写入内存的开销为:

img

2.2.5 Sorting the Result Set

因为在叶子节点中,被索引字段不是按照rowID的顺序存放的,是按照其自身的顺序存放的,所以查询出来的结果与顺序扫描不一致。如果需要保证与顺序扫描的输出结果一致,还需要进行一步排序,其开销为:

img

至此,对于单个查询,使用Secondary B+-Trees索引扫描的代价计算公式为:

img

其中,Si是这个查询的选择率。

2.2.6 Concurrent Index Access

对于多个查询,使用Secondary B+-Trees索引扫描的情况,需要多次遍历索引树,并将每一次查询的开销叠加起来,有以下公式:

img

为了计算方便,可近似将img替换为其最大的可能值img

3 Acces Path Selection

通过上述两中扫描方法的cost model,我们可以在选择access path的时候,分别计算两种方法的代价,并选择代价较小的一个。在上述公式中,选择率可以通过统计信息得到,硬件相关的属性是确定的,所以影响算子代价的因素就只有并发度q,以及总体选择率Stot。

 

4 实验结果

img

5 总结

(1)在内存数据库中,即使没有了I/O开销,二级索引扫描在某些场景下的开销依然小于顺序扫描。

(2)细粒度的access path选择除了要考虑选择率,硬件属性,数据分布,还需要考虑运行时的查询并发情况。

(3)Data set的大小很关键,在小数据集的场景下,顺序扫描完胜二级索引扫描,但随着数据集的增大,索引扫描优势将会显现。

(4)当缓存和内存延迟减少,或者内存带宽减少时,二级索引变得更有利。相反,较慢的缓存或内存,以及内存带宽增大会使得扫描变得有利。从这个角度讲,未来硬件的发展也将影响access path的选择。

 
posted @ 2022-07-08 15:36  jason_t  阅读(44)  评论(0编辑  收藏  举报