数据库常用的索引结构

时间:2020-04-28 20:31:38   收藏:0   阅读:69

数据库常用的索引结构

一个最简单的数据库:

#!/bin/bash

db_set() {
    echo "$1, $2" >> database
}

db_get(){
    grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}

底层存储格式:一个名叫database的纯文本文件。每一行都是一个键值对,每次调用db_set就在文本最后添加新内容,相同键值的旧内容不会被覆盖。db_get查找文件中最后一次出现的键值来找到最新值。

数据库使用的日志也是一个仅支持追加式更新的数据文件。如果使用上述最简单的数据库来保存日志,当日志中保存大量内容时,db_get函数的性能会很差,它是O(n)的。

为了高效查找数据库中特定的键值,需要引入索引。

哈希索引

假设数据存储全部采用追加式文件,那么利用哈希索引来解决上述问题就可以采取如下策略:内存中的HashMap把每个键值一一映射到数据文件中特定的字节偏移量。

技术分享图片

在这种策略下,为了获取某个值,只需要一次磁盘寻址就可以将value加载进内存。这种策略非常适合每个键的值频繁更新的场景。

由于我们采用的是追加式的更新,当数据非常多时可能会面临磁盘用尽的情况。解决方案:将日志文件(database文件)分解成固定大小的段,当文件达到一定程度大小时就关闭,并将后续写入到新的段文件中。同时,可以在关闭的文件中进行压缩,消除重复的键,只保留最新的键值对。对于这些段的更新可以交给后台线程完成。

技术分享图片

当对日志段进行压缩以后,其大小往往会小于符定段大小,那么还可以对多个被压缩过的日志段进行合并。这个合并过程也可以交给后台线程完成,在这期间被冻结的旧日志段依然可以读写。当合并完成后,后续的读写可以切换到新的合并段上。

技术分享图片

在实际使用中,还需要考虑很多其他问题,才能真正使该策略行之有效:

追加式日志的好处:

局限性:

SSTables和LSM-Tree

在哈希索引结构中,日志文件中的每个存储段都是一个键值对序列,键值对按照写入顺序排序,后出现的键值优于之前的值。

SSTable(Sorted String Table):排序字符串表。将段文件中的键值对按键排序,并且要求每个键在合并的段文件中只能出现一次(段文件压缩过程已经确保了)。

相比哈希索引的优势:

技术分享图片

技术分享图片

构建和维护SSTable

由于写入的键值会以任意顺序出现,需要解决如何将记录排序的问题。
基于磁盘的排序:B-trees
基于内存的排序:红黑树或AVL树

基于内存的红黑树或者AVL树的存储引擎工作流程:

为了防止数据库崩溃导致数据出错,还需要在磁盘上保留单独的日志文件。日志文件以记录当前内存表中的操作记录,当内存表被写入磁盘后,日志就可删除。

从SSTable到LSM-Tree

LSM-Tree(Log-Structured Merge Tree)

基于日志和压缩排序文件原理的存储引擎都被称为LSM存储引擎。

性能优化

在LSM-Tree中查找一个不存在的键会很慢:首先搜索内存表,然后回溯所有段文件。优化:布隆过滤器

还有对SSTable压缩和合并具体顺序与时机的优化。

B-trees

是几乎所有关系数据库中的标准索引实现。

B-tree将数据库分解成固定大小的块或页,传统上大小为4KB,页是内部读/写的最小单元。每个页面都可以使用地址或位置进行标识,页面之间依此标识进行相互引用。

技术分享图片

某个页面被指定为B-tree的根,所有查询都是从根页面开始的。
B-tree中一个页所包含的子页引用数量成为分支因子。上图中的分支因子为6。

添加新键时,首先找到范围包含该键的页,并将其添加到页面中,如果页面空间不足以容纳新键,则需要将页面分裂为两个半满的页,其父页面需要更新,以包含新分裂的页。
技术分享图片

B-tree总是保持平衡的:具有 n 个键的B-tree总是具有O(log n)的深度。

B-tree的基本写操作是使用新数据覆盖磁盘上的旧页。

关于使用B-tree的可靠性:
有些操作需要覆盖多个不同的页,例如,插入操作导致页溢出。这种操作往往比较危险,因为如果数据库在完成部分页写入之后发生崩溃,会导致索引结构破坏。

为了解决数据库崩溃之后数据恢复问题,可以采用预写日志(write-head log WAL)。预写日志是一个仅支持追加修改的文件,每个B-tree的修改必须先更新WAL再修改树本身的页。

优化B-tree

对比LSM-Tree与B-Tree

根据经验,LSM-Tree写入更快,B-Tree查询更快。

LSM-Tree的优点

B-tree索引必须写入两次数据,一次写入预写日志,另一次写入树的页本身。
LSM-tree索引时,由于反复压缩和SSTabel的合并,日志结构索引也会导致重写数据多次。

在数据库内,一次数据写入请求导致的多次磁盘写被称为写放大。

LSM-tree由于以顺序方式写入紧凑的SSTable,而不必重写树中的多个页,所以LSM-tree具有较低的写放大。所以LSM-tree通常能够承受比B-tree更高的写入吞吐量。

LSM-tree可以支持更好的压缩,因此磁盘上的文件比B-tree小很多。B-tree是基于页的,当页被分裂或者当一行内容不能适合现有页时,页中的空间无法使用。LSM-tree可以定期重写SSTable以消除碎片化。

LSM-Tree的缺点

由于LSM-tree的写入吞吐量很高,而磁盘的写入带宽是有限的。磁盘的写入带宽需要在初始写入(记录刷新内存表到磁盘)和后台运行的压缩线程之间共享。

当压缩没有被很好的配置时,就会发生压缩无法匹配新数据写入的情况。即,初始写入数据量太大,而压缩速率太小导致磁盘上未合并的段数量不断增加,导致磁盘空间不足。

原文:https://www.cnblogs.com/hezhiqiangTS/p/12796897.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!