从数据库管理系统收到一个业务数据请求,到完成数据检索后响应业务请求的过程为例,简化下可以得到如图下的一个简易示例数据模型。
图1
根据上述数据模型可估算,即使在采用顶级高性能存储设备(如延迟低至50μs的NVMe SSD)的理想场景下,存储介质物理延迟(I/O时间)仍占据数据检索请求处理链路的绝对比重。具体分解如下:
图2
由此可得初步结论:存储I/O时间在数据检索请求处理中占比超过90%。需特别指出的是,实际生产环境中极少存在50μs级别的存储设备(企业级SSD常态延迟为100-500μs,HDD更是都要毫秒以上),因此存储I/O占比往往进一步攀升至95%-99%区间,成为系统性延迟的核心来源。
如下图为某品牌的企业SSD产品参数,读延迟最低为75μs左右。
图3
经过以上简单分析不难看出,降低存储I/O的时间是提升数据检索操作效能的决定性因素,尤其在以数据检索为主要功能的关系型数据库管理系统中。
存储中的IO请求响应耗时主要集中在排队时间、寻址时间、传输时间三部分,我们根据这三部分整理性能点和优化方向预估如下表:
耗时部分 | 性能相关点 | 优化的大方向 |
---|---|---|
排队时间 | 和IO请求、存储调度、设备繁忙相关 | 减少IO次数 |
寻址时间 | 设备性能(旋转延迟)、数据页分布相关 | 1、设计存储时写入的数据页紧凑分布(顺序读、随机读)2、更换高性能设备 |
传输时间 | 设备性能,传输的数据页量多少相关 | 1、减少完整IO请求的数据量大小。(尽可能只获取需要的有效数据)2、更换高性能设备 |
显而易见的,在设备性能已经固定的前提下,除更换设备以外的优化方向就至关重要了,可以总结如下3点:
在上一章中,我们由存储I/O占比引申出数据库管理系统中需要优化的IO方向,那么需要满足以上优化方向的情形下,我们需要何种存储结构呢,因为数据物理存储结构是涉及存储设备的底层逻辑内容,这里只讨论基于数据页以上的数据的逻辑存储结构。
假设现在用户存在如下图4的一组数据,用户需要从数据中获取到CID=100200的数据,那么正常流程来是如何去做这个确认和获取动作的呢。
图4
这里是分为两个步骤,首先是定位CID=100025的数据,然后把该行所在所有数据提取出来。在定位的过程就需要用到查找算法了,而常见查找算法如下表:
查找算法 | 优点 | 缺点 | 前提条件 |
---|---|---|---|
线性查找 | 实现简单 | 对于大规模数据,效率较低,时间复杂度为O(n)。 | 无特殊前提条件,适用于任何线性数据结构。 |
二分查找 | 效率高,稳定性好,性能不受数据分布影响,时间复杂度为O(logn)。 | 要求数据有序 | 数据必须有序 |
插值查找 | 在均匀分布的数据中,查找效率高,时间复杂度优于二分查找。 | 仅适用于均匀分布的已排序数组,数据分布不均时效率显著降低。 | 数据必须有序且分布均匀。 |
分块查找 | 结合了顺序查找和二分查找的优点,适用于数据较多但不会频繁变化的场景。 | 对于大规模数据,块的划分和索引表的维护可能增加复杂度。 | 数据分成若干块,块内无序但块间有序,需要构建索引表。 |
哈希查找 | 平均时间复杂度为O(1),查找效率极高,适用于大规模数据集合。 | 需要设计哈希函数;哈希冲突无法避免,需要额外的解决机制。 | 需要设计合适的哈希函数,数据存储在哈希表中。 |
B树查找 | 适用于大量数据的存储和检索,能够通过减少磁盘I/O操作次数来提高查询效率 | 高度依赖磁盘I/O性能;对节点大小有要求 | 数据必须有序,适合大数据集,不存在重复元素,能够在数组中判断元素大小关系。 |
从以上的算法对比中不难看到,数据有序性是除了哈希查找外的绝大部分高级查找算法的基础,数据库管理系统作为一个需要对数据高频查找的系统必须要重视数据的有序性。
正如上面的例子,如果数据本身就是基于CID有序的话,那么很容易就可以直接利用各种高级查找算法来找到需要的数据。
仍以上面的CID、NAME、AGE列数据的例子,在上面我们探讨了高级算法必须有序的前提条件,那么如果我们每次查找前都去做一次排序,这个代价在大数据量面前显然也是不合适的。
于是我们希望数据本身有序,或者写入的时候按照有序写入存储,这个代价明显比后面每次需要查找时再排序小,但是这就和用户提供的数据原来的顺序有冲突了,怎么办呢?
很简单我们可以通过空间来置换时间,我们维护两份数据,一份是按照用户的顺序,一份是按照CID列顺序(图5),这样就能达到有序的目的,利用高级查找算法了。
图5
但是实际场景下是除了会通过CID列查找,还会通过NAME列、AGE列或者其他列去查找数据,那么如果存在更多列的情况下,我们还可能考虑维护这么多份完整数据吗?这个空间代价、更新代价就又太大了!
那么仅维护单列的有序数据是否可行?我们针对需要进行查询的列单独维护一份有序的列数据,同时为了满足需要其他列完整数据,我们可以在这有序的列数据中加入指向原始顺序的完整数据的逻辑映射地址,于是就可以得到了这个包含某一列有序数据加上原始完整行数据映射逻辑地址的独立数据集(图6),当通过该列进行条件查找时,就可以利用高级算法快速完成数据定位,把需要的数据读取出来了。
图6
而这就是索引的原始思路,以空间换取时间,加速查找定位数据的过程,同时所需要的空间代价也趋于一个可以接受的状态,至少不会造成成倍以上的冗余数据,当然这里是简化的一个讨论过程。
实际索引的设计需要考虑更多因素,因为数据的检索不止等值还有范围,原始数据也不会是一成不变的,存储性能依旧是性能瓶颈上限所在。
所以索引的设计不仅需要满足等值查询(随机)、范围查询(顺序)的优化,还要考虑原数据的新增写入、修改更新、删除的维护代价,以及贴合存储I/O相关的优化方向。
(未完待续...)
文章
阅读量
获赞