读书笔记(第三章 数据存储与检索)
主要是在确定了数据模型以及查询语言的基础上,明确数据库如何存储输入的数据,并且如何进行数据查找
- 有利于我们根据业务需要(如负载以及使用情况等),挑选最适合的存储引擎
存储所需数据结构
单纯地记录数据,使用遍历的方式进行数据查找,复杂度在O(n)级别
可以采用空间换时间的方式,用额外的信息来加速查找过程,在最坏的情况下也只是和遍历的复杂度一致
- 在联合索引中什么时候会出现这种索引失效而需要进行全文遍历的情况?
使用索引也会带来额外的负担
- 会使用额外的空间存储索引
- 在写入的时候,需要进行索引的维护,有额外的时间成本
日志追加式索引(LSM)
数据存储与读取的设计:
-
追加式:写入数据只能追加在文件结尾
- 当追加的值很多的时候,可以进行段(段用于应对磁盘存储空间不足的情况)压缩,将一些过期的、不会用到的记录去除(如计数器以前的计数值)
- 对于每一个段有自己独立的索引
- 其设计是非常不错的
- 写入记录以及分段压缩合并都在顺序操作,会比随机的原地修改操作快得多
- 支持非常高的写入吞吐量
- 并发以及崩溃回复要简单得多,由于是追加的,只会丢失最后一次操作
- 段压缩以及段合并可以避免存储碎片化问题
- 缺点
- 段的查找问题,对于不存在的记录,需要遍历所有的段才能得出结论
- 当追加的值很多的时候,可以进行段(段用于应对磁盘存储空间不足的情况)压缩,将一些过期的、不会用到的记录去除(如计数器以前的计数值)
-
哈希索引
内部数据结构:hashmap
可以通过内存中key、value的形式来记录对应的record,value可以为对应记录在磁盘的偏移量,在Bitcask中就是这样的,其提供了高效的读写服务
缺点:
- 需要将hashmap放入内存才能够提供高效的查找服务,当有很多key的时候,没有办法完全放入内存
- 需要处理键值冲突
- 不支持范围查找以及排序等操作
- SSTables
让段中的数据按顺序进行存储(这点可以通过磁盘的组织方式实现),hashmap中也就不需要存储每一个key了,可以只记录部分稀疏的key,通过其有序性来找到对应的记录。
压缩段的过程与归并排序类似
写入时:
- 写入内存的平衡树
- 大于内存的时候,将内存的输入作为新段写入磁盘
- 后台周期性地执行段合并以及压缩操作
查找时:
- 先在内存中进行查找
- 在磁盘中从新到旧对段进行查找操作
缺点:当出现崩溃的时候,内存中的临时数据都会丢失
基于排序与合并文件的存储引擎通常被称为LSM存储引擎、LSM-Tree
- 性能优化
- 查找不存在的键时,会遍历所有的段,效率较低
- 可以使用布隆过滤器
- 段压缩以及合并的顺序时机
- levelDB、RocksDB、HBase等使用的方式与策略也都不一致
- 查找不存在的键时,会遍历所有的段,效率较低
B-Tree索引
作为关系型数据库中的普遍索引存在
-
其内部也像SSTables一样,保持键值对有序性
-
将数据库分解为固定大小的块或者页
-
将固定大小的页之间以指针的方式进行组织,形成树状结构
-
键值的插入有专门的分裂以及旋转算法维护,复杂度o(lgn)
-
修改操作直接进行页覆盖,此操作较为危险,如果中途发生崩溃则会导致数据的错误
- 往往有额外的备份数据(如预写文件,WAL)
- 多线程访问的时候,要注意并发控制问题
优化:
- 采用写时复制的策略代替WAL,不是直接进行页的覆盖,而是另其一页,后续调整ref的调用即可
- 对键大小进行压缩,只需要保留起止范围的信息即可,可以有效降低树高
- 尽量保持页之间一定的顺序,尽可能用顺序IO替代随机IO
两者的对比
两者都是key-value索引
LSM-Tree:
优点:
- 追加模式带来的更高的写入速度
- B-Tree在分裂等情况下,会需要多次的写入
缺点:
- 压缩的消耗较大,会pending正常的读写,导致会随机的出现读写性能下降的情况
- 压缩和正常的读写共享带宽,当数据量大时,需要更多的资源、带宽来进行数据、段的压缩,如果分配的资源不够多,会导致压缩的速度跟不上写入的速度,导致大量数据积压
B-Tree:
优点:
- 每一个数据都是唯一的,更容易实现事务,而LSM-Tree可能会在多个段中存在不同时间的副本
其他索引
- 主索引与二级索引
- 维护索引之间的一致性问题
- 覆盖索引可以加速某些查找
- 需要额外的存储以及更多的写入代价
- 多列索引
- 联合索引
- 索引失效问题
- 全文索引以及模糊索引
- Lucene支持某个编辑距离内的文本搜索,内部是以类似字典树与状态机的方式进行的实现
- 内存数据库
- 其性能优秀的根本原因是:避免使用写磁盘的格式对内存数据结构编码的开销
- 某些磁盘数据库可能也不需要IO,因为需要的热点数据可能都在内存,但是依旧没有内存数据库的性能高
- 为什么不在加载、写入的时候才进行解码、编码呢?
- 使用磁盘以及SSD,需要进行精心的数据编排才能够获得足够好的读写性能
- 某些磁盘数据库可能也不需要IO,因为需要的热点数据可能都在内存,但是依旧没有内存数据库的性能高
- 可以使用类似虚拟缓存的方法来提高内存数据库支持的数据量大小
- 其性能优秀的根本原因是:避免使用写磁盘的格式对内存数据结构编码的开销