这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战
主要是在确定了数据模型以及查询语言的基础上,明确数据库如何存储输入的数据,并且如何进行数据查找
- 有利于我们根据业务需要(如负载以及使用情况等),挑选最适合的存储引擎
存储所需数据结构
单纯地记录数据,使用遍历的方式进行数据查找,复杂度在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,需要进行精心的数据编排才能够获得足够好的读写性能
-
\
-
- 可以使用类似虚拟缓存的方法来提高内存数据库支持的数据量大小
\
事务处理与分析处理
事务(广义):一个逻辑单元的一组读写操作
重要有两种用途:
- 一种是交互式的在线事务处理(OLTP)
- 一种是用于数据分析(OLAP),一般被称为数据仓库
OLTP与数据仓库的一些差异:
- 两者根据自己的查询模式进行了各自的优化
- 数据从仓库最常见的是关系型
数据仓库
星形及雪花分析模式:以一个表为中心,将其他详细信息以外键的形式连接在一起
列式存储:
实际上就是行列交换,使得列数据之间是顺序存储的,在列的查找上有着更高的性能
列式存储的压缩
对于有很多重复值的数据更加容易压缩,该列很多取值都是重复的,可以类似one hot矩阵的思想构建稀疏矩阵,以稀疏矩阵存储优化的思想进行存储的优化,使用key以及次数来表达一个存储
至于压缩后的数据插入,和LSM-tree有着异曲同工之妙,都是key-值的统计合并,同时要保证一定的存储顺序
对于常用的一些聚合函数,每次都进行运算会导致效率降低,采用缓存的方式(类似前缀和的感觉)进行一定的数据缓存可以加快数据的获取,被称之为物化视图,在写的时候会加大负担,对于读频繁的数据库更加有意义
内存带宽与矢量化处理
数据瓶颈:
- 只有在内存才能进行查询等操作,因此查找数据很大的时候就会面临了数据从磁盘加载到内存的带宽瓶颈
- CPU指令问题
- 列压缩可以使得更多的数据进入CPU缓存,可以让CPU可以更快进行数据操作
列式存储的排序
某一列排序是没有意义的,需要保持同一行中的数据联系
排序有利于相同值的聚集,更加高效地进行数据的压缩
可以尝试在冗余数据库采取不同的属性排序优先级,在查询的时候可以使用最优策略
列式存储的写入操作
类似LSM-Tree的操作过程
聚合优化
并不是所有的数据库都是列式数据库
传统的关系型数据库聚合函数,可以通过物化聚合的方式进行加速,将一些常用的聚合数据进行缓存
物化视图:将对应的查询结果写入磁盘进行持久化
在进行数据库写入的时候也需要进行物化视图的更新,因此这种方法实际上也会影响写入性能
这种方法能够进行查询优化的查询并不是很多,有着较大的限制