数据库的日志系统
许多数据库内部使用日志,日志是一个只支持追加更新的记录序列的集合,通常存储了大量的数据。如果每查询一条数据要从头到位扫描整个数据文件来查找数据所在的位置,查找的开销是O(n),并不是是十分友好。为了高效从数据库中查找到特定的值,需要新的数据结构:索引。
索引是基于原始数据派生而来的额外数据结构,许多数据库允许单独增加和删除索引,而不影响数据库的内容,只影响查询性能。维护额外的数据结构必然带来额外的写入开销。对于写入,很难超过单纯以追加文件方式的性能。
适当的索引可以加速读取查询,但每个索引都会减慢写速度。
key-value哈希索引
key-value类型不是唯一可以索引的数据,但是它是其他更复杂索引的基础构造模块。key-value存储和大多数编程语言所内置的字典结构十分相似,通常使用hash map/hash table来实现。
1、哈希表索引的局限性与优点
局限性:_
- 哈希表必须全部放入内存,如果有大量的key,可能存在内存不足的现象。原则上可以在磁盘上维护hash map,但是当哈希变满的时候,哈希表继续增长需要付出的代价比较昂贵,并且哈希冲突时候需要更加复杂的处理逻辑。
- 区间查询效率不高。比如不能简单支持扫面abc0000和abc9999区间内所有的键,只能逐个查询。
因此为了解决这些局限性,后续会介绍其它索引针对不同缺陷所进行的优化方式。
优点: - 追加和分段合并主要是顺序写,比随机写入快。
- 若果段文件是追加的或者不可变的,并发和崩溃恢复则简单许多,比如不必担心在重写值时发生崩溃的情况,留下一个包含部分旧值和部分新值混杂在一起的文件。
- 合并旧段课程避免数据文件碎片化。
2、哈希表索引的简单模型
使用类csv格式的文件存储key-value对,将key存入内存中对hash map进行索引,每次写入只能进行追加操作。
如果只追加到一个文件,但是磁盘空间是有限到,为了避免耗尽空间,需要将日志分解为一定大小的段,当文件达到一定大小的时候就关闭它,将后续写入到新的文件中,然后压缩这些段文件(去掉重复段键,只保留最新更新)。
由于段在写入后不会更改,所以被合并的段需要写入另一个新的段文件。段文件段合并和压缩过程中会将旧的段文件冻结,当其它线程运行时依旧可以使用旧的段文件进行正常读取和写请求,当合并完成后将读取请求切换到新段合并段上,旧的段文件可以安全删除。
需要解决的一些细节问题:
- 删除记录
在需要删除的key的位置添加删除符,在合并段的时候抛弃/ - 奔溃修复
机器重启后内存中的hash map可能会消失,可以将每个段的hash map的快照存在磁盘上。 - 并发控制
单线程写入保证严格的先后顺序,多线程读取。 - 部分写入
数据库随时可能奔溃,包括将记录追加到日志的过程中,因此需要有一个校验值发现损坏的部分并丢弃。