3 数据存储与检索
3.1 数据库核心:数据结构
3.1.1 哈希索引
- hashmap存放在内存中,把每个键一一映射到数据文件中特定的字节偏移量,也就是保存每个值在磁盘中的位置
- 采用日志的方法,把key,value追加到文件末尾。对于删除的记录,可以使用特殊标记。
- 定期将日志分解成多个小文件(日志段),并使用后台线程对小文件进行合并压缩
- 缺点
- 哈希表必须全部放入内存
- 区间查询效率不高
3.1.2 SSTables和LSM-Tree
-
SSTables:要求哈希表中的key-value对的顺序按键排序
-
SSTables相比哈希索引的日志段具有的优点:
- 因为每个段内的键是有序的,因此合并多个段内的key-value更高效。(段内有序,并且以最新的段内的键值对为准)
- 查找某个键值对也更高效
-
构建和维护SSTables:
- 使用红黑树或AVL树等内存排序数据结构维护key-value的有序性
- 基本工作流程:
- 写入时,将其添加到内存中的平衡数据结构(如红黑树,跳表)中,也称为内存表
- 当内存表大于某个阈值时,将其作为SSTables文件写入磁盘。新的SSTable文件作为数据库的最新部分。当这个SSTable写磁盘时,写入可以继续添加到一个新创建的内存表中
- 读取时,首先在内存表中查找,然后找最新的日志段文件,然后找次新的,以此类推
- 后台进程周期性地执行段合并与压缩,合并多个段文件,并丢弃那些已经被覆盖或删除的值
-
如何避免数据库崩溃时,在内存中的内存表数据丢失?
- 在磁盘上保留单独的日志,每个写入立即追加到该日志,该日志无需按键排序,用于在崩溃后恢复内存表。每当将内存表写入SSTables时,这个备份日志文件中相应的日志可以删除
-
相关产品:LevelDB,RocksDB,Cassandra和HBase
-
LSM存储引擎(Log-Structured Merge-Tree):基于合并和压缩排序文件原理的存储引擎
-
LSM补充
- https://www.jianshu.com/p/5c846e205f5f
- https://www.bilibili.com/video/BV1Zz4y1r7BJ
- https://www.cnblogs.com/fxjwind/archive/2012/08/14/2638371.html
-
Lucene中,从词条到posting list的映射关系保存在类SSTable的排序文件中,这些文件在后台合并
-
性能优化方法:
- 查找某个不存在的键时,LSM需要从内存一直找到最旧的段文件,此时使用布隆过滤器快速判断某个键不存在。
- SSTables大小分级和分层压缩策略
3.1.3 B树
- B树将数据库分解成固定大小的块或页,页是内部读写的最小单元。页之间可以相互应用,通过磁盘地址,而不是内存。页组织成多叉树的形式。
- B树有如下特点(补充):
- 所有键值分布在整颗树中(索引值和具体data都在每个节点里);
- 任何一个关键字出现且只出现在一个结点中;
- 搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据);
- 在关键字全集内做一次查找,性能逼近二分查找;
- 可靠性:
- 每个B树的修改必须先更新WAL(write-ahead log)预写日志,然后再修改树本身的页。当数据库在崩溃后需要回复时,该日志用于将B树回复到最近一致的状态。
- 优化B树:
- 写时复制->并发控制
- 进一步压缩键,节省页空间
- 相邻子页按顺序保存在磁盘
- 同级兄弟节点之间相互引用
3.1.4 B+树和B树(补充)
- B+树改进了B树,内部节点不存数据,只存指向子页节点的引用,这样每个内部节点可以指向更多的子页,树的高度能进一步压缩,磁盘IO更少,检索时间更短
- 数据存放在叶子节点,叶子节点间以链表形式,更高效地支持范围查询
3.1.5 B树和LSM树的对比
- LSM树写入更快,B树读取更快。
- LSM树属于日志结构流派,只运行追加式更新文件,不会修改文件。B树属于原地更新流派
- LSM树的优点
- 磁盘顺序写,比B树随机写要快
- 支持更好地压缩。B树中的页存在碎片。LSM树定期重写SSTables以消除碎片
- LSM树的缺点
- 压缩操作可能影响读写操作,包括占用磁盘IO,这时真正的读写操作需要等待
- 要衡量好压缩操作和读写操作占用磁盘带宽的比例。如果压缩操作占用磁盘带宽少,就可能导致磁盘上未合并段的数量不断增加,影响读性能,甚至导致磁盘空间不足。
- LSM可能在不同的段中具有相同键的多个副本,B树则只有一个。
3.1.6 其他索引结构
- 索引的键是查询的对象,值可以是以下两种:
- 值的实际内容
- 值的地址引用,这种情况下边,存储行的具体位置称为堆文件
- 聚集索引和非聚集索引
- InnoDB中主键都是聚集索引,二级索引引用主键的位置
- 覆盖索引:在索引中保存一些表的列值,无需回表操作,就支持只通过索引即可得到某些查询的结果(这种情况下,称索引覆盖了查询)
- 多列索引
- 全文搜索和模糊索引
- Lucene能够在某个编辑距离内搜索文本
- 内存数据库
3.2 OLTP和OLAP
-
OLTP和OLAP的特征对比
-
SQL能用于OLTP和OLAP,但如今的趋势是,放弃使用OLTP系统用于分析目的,而是在单独的数据库上运行分析,即数据仓库
- 在OLTP上执行临时分析查询,代价高,通常需要扫描大量数据,会损害并发执行事务的性能
- OLTP机制不适合数据仓库,OLAP需要在大量行中顺序扫描,索引的有效性显著降低,此时最重要的是紧凑编码数据,减少磁盘读取的数据量。
- 从OLTP导入数据到数据仓库(ETL,Extract-Transform-Load)
- 数据仓库针对分析访问模式进行优化
-
OLAP产品:Teradata、SAP HAHA等,基于Hadoop的SQL项目如Hive,Spark SQL、Facebook Presto、Apache Tajo和Apache Drill等
-
星型与雪花型OLAP模式
- 事实表和维度表。事实表位于中心,引用维度表。
-
数据仓库表通常非常宽
3.3 列式存储
3.3.1 目的
- 数据仓库的事实表中列数量多,通常超过100列
- 数据仓库很少执行select *操作,而是select指定的某几列
- 面向行的存储:数据按行存储,读取出整行数据,再解析过滤出所需的列
- 面向列的存储:数据按列存储,查询只需要读取和解析某些列,可以节省大量工作
3.3.2 列压缩
- 面向列的存储更适合压缩
- 如果列中不同值的数量小于行数,可以使用位图编码
3.3.3 列存储中的排序
3.3.4 列存储的写操作
- 面向列的存储、压缩和排序有助于加速读取,但导致写入困难
- 参考LSM树,新插入的数据,首先写入内存存取去,累计到足够数量时写入磁盘,与磁盘上的列文件合并。
3.3.5 聚合:数据立方体与物化视图
- 对于聚合操作结果的缓存
- 物化视图
- 数据立方体
、压缩和排序有助于加速读取,但导致写入困难
- 参考LSM树,新插入的数据,首先写入内存存取去,累计到足够数量时写入磁盘,与磁盘上的列文件合并。
3.3.5 聚合:数据立方体与物化视图
- 对于聚合操作结果的缓存
- 物化视图
- 数据立方体