想象一个最简单的数据库,数据以key-value形式追加写入,数据查找时从头到尾扫描整个文件。这种方式在数据量比较大时,查询性能很差,这就需要新的数据结构:索引。
哈希索引
描述:
- 索引hashmap保存在内存中。
- 为了避免磁盘写满,日志分成一定大小的段,之后进行段的压缩和合并,相同key值的value会被合并。
局限性:
- 哈希表全部放到内存中,内存空间有限。如果放入磁盘会有大量随机io,数据量持续增大会有哈希冲突等。
- 区间查找效率低。 使用的数据库:Bitcast
LSM存储引擎
SSTable: 段文件按照key进行排序,可以使合并段更加高效,并且索引不需要保存所有的键值,可以使用稀疏索引。
- 写入时,添加到内存中的树中比如红黑树。
- 大于某个阈值,作为SSTable写入磁盘。
- 查询请求,先在内存表中查找,再查找最新的段,以此类推。
- 周期进行段合并和压缩。
B-tree
页是内部读写的最小单元(4KB或者8KB),通过索引找到数据行所在的页,然后把页加载到内存中进行查找。
数据插入,覆盖磁盘上的页,随机写入。如果叶子节点已经满了,拆分页,如果在这个过程中发生故障会索引会被破坏。引入WAL(预写日志),先更新WAL再更新本身的页,当数据库故障后使用WAL将数据库恢复到一致状态。
从原理来说,b+树在查询过程中应该是不会慢的,但如果数据插入比较无序的时候,比如先插入5 然后10000然后3然后800 这样跨度很大的数据的时候,就需要先“找到这个数据应该被插入的位置”,然后插入数据。这个查找到位置的过程,如果非常离散,那么就意味着每次查找的时候,他的子叶节点都不在内存中,这时候就必须使用磁盘寻道时间来进行查找了,比如树高度为3,就会有3次随机io。更新基本与插入是相同的。
LSM vs B-tree
- LSM相比B-tree有更好的写入性能。顺序写入而不必重写多个页,顺序写比随机写快得多。LSM-tree可以更好地支持压缩,存储空间比B-tree小很多。
- 段合并和压缩有时候会影响读写。比如ck的too many parts merge错误。
- B-tree的健和索引全局对应关系,而段的索引对应的健可能在多个段中有。事务隔离通常是通过健范围上的锁来实现的。LSM树不支持事务。
- 如果查单条数据 B-tree性能更好。
事务处理 vs 分析处理
通常使用某些键查找少量记录,输入或者更新记录,交互式的程序,称为联机事务处理(OLTP)。
扫描大量记录,每个记录读取少数几列,并计算汇总结果,在线分析处理(OLAP)。
OLAP通常使用LSM引擎和列存储。
ClickHouse存储引擎
LSM读性能一般,读取时可能需要先看是否命中内存,否则需要访问较多的磁盘文件。所以说基于LSM树实现的系统读取性能不是很高,可以采取一些策略进行优化以提高读性能。
ClickHouse使用MergeTree,和LSM的差别是不会写内存,没有MemTable,直接排序之后顺序写磁盘。
那么ck是如何优化读性能呢?
按照分区键分成不同的目录,类似于LevelDB,分层进行段合并,可以减少检索的文件个数,并且减少了一次合并操作带来的影响。按照目录进行段合并,合并后生成新的目录新的索引文件。不存在全局的索引文件,需要从每个目录中进行查找。minmax_{colume}.idx文件存储着整个分区键的min/max,用于分区裁剪,直接过滤掉不符合条件的data part,一定程度上舒缓了LSM-Tree的Range Query的读放大问题。
写入达到阈值后压缩落盘生成block。索引为稀疏索引,颗粒度为8192行,一个block中可能会有多个索引。mrk文件保存了主键和对应的block以及block中的偏移量,可以快速查找。