本章我们从数据库的角度来探讨探讨如何存储输入的数据,并且在收到查询时找到数据。
针对事务型负载和针对分析型负载的存储引擎优化存在很大的差异。
日志通常指的是应用程序的运行输出日志,来记录发生了什么事情。当然日志还有一个更通用的含义,表示一个仅能追加的记录序列集合。它可能是人类不可读的,可能是二进制格式而只能被其他程序读取。
日志方式的查找开销是 O ( n ) O(n) O(n)。
为了高效查找数据库中的特定键的值,需要新的数据结构:索引。索引的基本想法是保留一些额外的元数据,将这些元数据作为路标,帮助定位想要的数据。
索引是基于原始数据派生而来的额外数据结构。很多数据库允许单独添加和删除索引而不改变数据库的内容,以影响查询性能。对于写入,任何类型的索引都会降低写的速度(相比于日志的简单追加文件方式)。
所以适当的索引可以加速读取查询,但每个索引都会降低写速度:这是存储系统中重要的权衡设计。
假设数据存储全部采用追加式文件组成,最简单的索引策略是保存内存中的hash map,把每个键一一映射到数据文件中特定的字节偏移量,这样就可以找到每个值的位置。当查找某个值时,使用hash map来找到文件中的偏移量,即存储位置,然后读取内容。
如果key比较少,但每个key都有大量的写操作,将所有key保存在内存中是可取的。可以采用上述方式。
具体可见日志结构的存储引擎-bitcask。
哈希表索引的缺陷:
上述的日志结构的存储段中的键值对是按照写入顺序排列的。如果我们希望按照键对键值对排序,这种格式称为SSTable,它有以下优点:
构建和维护SSTables
存储引擎的基本工作流程如下:
以上的算法本质在很多引擎使用,如LevelDB,RocksDB,类似的存储引擎还有Cassandra和HBase。
LSM-Tree的基本思想是保存在后台合并的一系列SSTable。基于合并和压缩排序文件原理的存储引擎通常都被称为LSM存储引擎。
B-tree同样保留按键排序的键值对,以实现高效的键值查询和区间查询,但是它本质上具有非常不同的设计理念。
不同于之前看到的日志结构索引将数据库分解为可变大小的段,通常大小为几兆字节或者更大,并且始终按照顺序写入段。B-trees将数据库分解成固定大小的块或者页,传统上大小是4KB,页是内部读/写的最小单元。
由于每个页面都可以使用地址进行标识,就可以让一个页面引用另外一个页面。可以用这些页面引用构造一个树状页面,索引一个键时,从根页面开始逐渐向下寻找,直到到达一个包含单个键的叶子页,它包含每个内联键的值或包含可以找到值的页的引用。
分支因子是一个页中包含的子页的引用数量,实际中通常为几百个。
更新策略和崩溃恢复策略:
根据经验,LSM-tree通常对于写入更快,B-tree读取更快。
LSM-tree的优点:
由于B-tree存储引擎按固定页写入,那么通常每一页都会有些空间无法使用。而LSM-tree不是面向页的,并且定期重写SSTable以消除碎片化,所以具有较低的碎片率。
LSM-tree通常能够承受比B-tree更高的写入吞吐量,部分原因是具有较低的写放大(一次数据写入导致多次磁盘写入称为写放大),部分原因因为它是以顺序方式写入紧凑的SSTable文件。
LSM-tree的缺点:
日志结构存储的缺点是有时候压缩过程会干扰正在进行的读写操作。另外写入吞吐量和压缩要共享磁盘的有限的写入带宽,也就是说数据库的数据量越大,压缩就需要越多的磁盘带宽。
B-tree的每个键恰好唯一对应于索引中的某个位置,而日志结构的存储引擎可能在不同的段中具有相同键的多个副本。如果在这种场景下需要提供强大的事务语义,无疑B-tree更具竞争力。
在索引中存储值
多列索引
全文搜索和模糊搜索
在内存中保存所有内容
待补
本章的重点在于介绍数据库内部如何处理存储和检索,例如存储新数据和查询数据。
存储引擎大致分为两大类:针对事务处理(OLTP)优化的架构,以及针对分析型(OLAP)的优化架构。
在OLTP中分为两个主要流派: