浅谈存储引擎

最近一段时间在看一些数据库内部实现的相关知识,一直想找个时间总结下,感受学习这些知识会以为挺有意义的,做为一个开发人员你可能不会本身被要求去实现一个数据库,不过你应该要去理解数据库的实现原理,这样你才能根据你的场景更好的优化你的应用,而后也会在针对你的应用在进行数据库选型时提供帮助。mysql

目前的话,市面上有各类各样的数据库,有sql,nosql,newsql具体能够看我前段时间总结的一篇文章形形色色的数据库,那么他们的实现究竟是怎么的?或则都是用的什么存储引擎?再而后存储引擎各自的优点,应用场景是什么,我看了一些资料,这里简单总结一下,文末提供了相关资料。git

最简单的数据库github

用vim 定义db shell文件,输入下变的内容,就实现一个最简单的数据库,只有set, get方法redis

#/bin/bash

db_set() {
  echo "$1, $2" >> databases
}

db_get() {
  grep "^$1," databases | sed -e "s/^$1,//" | tail -n 1
}

[[ -z "${1-}" ]]
case $1 in
    db_set|db_get) "$1" "${@:2}" ;;
    *);;
esac
复制代码

set数据算法

$ ./db db_set test_key test_value
$ cat databases

test_key, test_value
复制代码

get数据sql

$./db db_get test_key
test_value
复制代码


简要分析shell

简要的分析一下,上边的数据库经过set方法会一直append文件到databases文件中,只要数据存储充足,对于数据库来讲是顺序写入,因此性能是很高的,这块至关因而log操做,只有append。而后是get方法,每次都须要grep整个数据库文件,至关因而全表扫描,从算法的角度来分析复杂度会是O(N),随着数据文件的增长,性能会愈来愈低,速度会很慢。数据库

因此为了优化存储get方法结构,咱们须要额外引入一个结构,索引结构(这个先不考虑并发控制,事务,范围查询等等常见的功能),索引的详情能够看连接,简单来讲它的目的是保存key和value的对应关系,就是位置对应关系,因此能够帮助咱们提高查询性能,同时也会带来一些弊端,这就要求在数据插入前同时更新索引数据,保持索引数据和内容数据的一致性,会影响写入性能。正式这样的缘由,数据库默认是不会开启索引的,索引建立会是很影响数据库的性能的一个指标,须要根据不一样情形建立合适的索引,索引太多,会影响写入性能,索引太少,影响查询性能,因此找到一个合适的平衡点,是很是重要的,大多数状况下,索引都是须要dba和开发一块儿评估。vim

HASH索引数组

先用最简单索引结构来分析,hash索引,hash是一个很是基本的算法,具体能够看连接。这里列举一个主要利用hash存储引擎的数据库Bitcask官方文档详细的介绍了其实现原理,简单来讲就是这个该数据库在内存中是一种hash表结构,hash结构维护key到文件存储的position,数据仅支持追加操做,全部的操做都是append增长而不更改老数据,数据文件分为新数据文件和老数据文件,老的数据文件只读不写,只有一个数据更新,及活跃数据文件。

A Log-Structured Hash Table for Fast Key/Value Data

数据结构以下:

Bitcask在内存中保存了key,value的位置关系,索引关系,key对应数据key, value保存不是实际的数据值,而是保存对应文件的相关信息,从图中能够看到是 file_id, value_sz, value_ops, tstamp,因为内存实际不保存真实数据,全部存储容量就会大大提高。

内存结构


file_id 表明保存的具体文件id

value_sz 表明value大小

value_ops 表明在文件中具体位置

tstamp 保存时间

而后还有一个点是,该数据实现的高性能是依赖于文件追加的方式,至关于LSM的数据追加,数据增长,删除,更新都是追加操做,造成新的数据版本,老的数据仍是会在磁盘上,因此操做对应的io都是顺序io,不会随机访问磁盘,因此写入性能获得了保证,而后对于过时的数据,脏数据,bitcask会按期在后台进行一个merge操做,将inactive的数据清除。对于读操做就不须要过多介绍了,经过key能够直接读取到values_文件位置,性能也能获得保证。

因为全部的数据都是保存在内存,全部服务或则主机挂掉,内存中的映射关系就会丢失,因此须要将内存中的hash结构数据数据持久化到磁盘,当数据服务从新启动,会从磁盘恢复对应关系。

下图是数据文件中保存的内容

实际文件中具体记录的数据以下,包含crc校验值,key, value等。


磁盘结构


试想一下,若是上边shell数据库,精简优化,利用hash索引保存key,value映射关系,那么就能提升数据的访问速度,避免全表扫描这种操做,这也是hash索引能带来的最大优点,可是试想一下,你的数据须要保证有序性,key有必定的顺序,那么hash索引就不能解决这类问题。

LSM

引入LSM算法能够保证写入性能的状况下支持还范围排序的。

首先说一下什么是LSM算法(Log Structured-Merge Tree), 最初LSM来自于Google分布式表格系统BigTable的论文,它是bigtables的文件组织方式。目前LSM已经应用到了多种数据库领域,最简单的是LevelDB,而后是facebook针对其更改的Roskdb,后来又产生了带上Hbase, cassandra数据库,在mongdb Wired Tiger也能看到,最近也出如今了MySQL领域,已经被普遍验证的存储引擎方式。

这里我简单介绍一下该类数据库的流程(这里以leveldb结构介绍)

leveldb


LSM会维护一个内存有序的Memtable,在内存中维护这样一个有序的数据结构会很容易,像红黑树,平衡树,当有数据插入是先写入到Memtable保持平衡,有序,当Memtable到达必定大小会触发Merge操做,首先将源Memtable指针赋值给一个不可变的MemTable, 这里叫Immutalbe Memtable,而后会从新生成一个Memtable,后续的操做会新的Memtable中操做,产生的Immutalbe Memtable会持久化为.sst文件,因为数据已是有序的了,因此持久化也会很容易。

组织结构

而后sst文件会是一种层级文件,sstable中的文件也是根据主键顺序排序的,每一个文件都会记录其中记录中的最小值和最大值(查询速度会有所保证)。leveldb中manifest文件保存了每一个sstable中的范围信息,和层级信息,leveldb在运行中 sstable也会进行合并,老的数据也会被清除,manifest保存了相关过程,低level的文件会合并为高level的文件。

这里想谈一下log文件,这里的log会记录写操做,就是因此写操做前必须将数据有限顺序写入到log中,而后再写入到内存中,这里你会想起数据库的redo log实现相似的事情,这里很简单,和其余标准数据库同样,为了解决内存数持久化到磁盘中可能出现的故障,如机器异常,内存故障等问题,优先顺序写入磁盘,因为这里是顺序的磁盘io,全部性能也不会有很大的顺势,能够这么说,大多数数据库为了保证数据的可靠性都确定会有预先写log操做,固然该log也不会无限增大,大多数数据库会采用checkpoint保存必定时间数据变化。

LSM的写操做和Bitcask同样,是经过append log的方式,只是会附加一点须要保持数据在内存中有序,不过插入,更新,删除都仍是顺序io,性能会有必定保证。而后后台会按期合并各个sstable,清除没必要要数据。这里可能引入的一个性能瓶颈是读取性能,若是是查询一个不存在的key,会先查找Memtable,而后会查询最新的sstable文件,这里会引入一个布隆过滤器来解决该问题,简单来讲他能很快的断定某数据集中是否存在某个key。

最后简单来讲,在保证必定写入和读取性能的条件下,比hash index的优点在于,提供了有序的数据集,支持了范围查询。

BTREE

上边介绍的hash和lsm都不是最应用普遍的索引,btree才是应用最普遍的数据结构。

B+索引是数据库中最多见,也是使用最频繁的索引结构,下面这里能够经过一些基础数据结构来解释会好一点。

相信你们都了解过一种基础的算法,二分查找法,以有序中点为间隔,使用跳跃式的方式,能快速将一组有序的数据结构中找到须要查找的值。如在下列数组中查找到48,使用3次就能够了。

二分查找

相对应的树数据结构是二叉查找树,二叉查找树定义是,二叉查找树左边的叶子节点老是小于父节点的节点的值,右节点老是大于父节点的值。该树能够经过前序,或则中序遍历获取到有序数组数据,而后经过二叉查找获取到数据。


二叉树


有序性能提升查询效率,有序性也能给数据库带来支持像范围查询提供条件。

其次为了更好的提高查询性能,咱们应该减小树的层级结构,如平衡二叉树(符合二叉树的定义,增长条件: 任何两个节点的高度差不得超过1),咱们须要尽量保证一棵树的平衡,不过保证一棵树的平衡须要带来必定的开销(经过旋转维护平衡),因此设计数据结构须要考虑查询效率,也须要考虑维护一棵树的成本。

btree,二叉树,平衡二叉树同样都是经典的数据结构。是由B tree演进而来,关于B+ tree,能够看链接详情。简单来讲就是 b+树的全部节点都是按照大小顺序的保存在同一层的叶子节点上,各个叶子节点经过子节点的指针进行链接,以下图。


btree+


这里简单说一种最经常使用的数据库Mysql Innodb,Innodb由btree实现索引,和hash索引相比,因为有序性,因此Innodb支持范围查询,在Innodb中,有一种叫汇集索引的特殊索引,他会保存将具体的数据行内容。

Innodb是按照page的方式来管理数据,每一个page对应一个节点,叶子节点保存了具体的数据,非叶子节点保存了索引信息,数据节点在b tree+ 有序,查询来说是从根节点二分查找,查找对应的数据,若是数据不存在,就从磁盘中读取,而后更新内存数据,修改数据和前边内容同样,都会先写入日志(防止异常状况数据丢失),这里对应redo log。对应插入操做会比较复杂,这里就不分析了,简单来讲就是为了保持b tree+树的平衡,会带来树的旋转和分裂,分离过程都是内存操做是很是危险的,因此也会进行也须要先写入redo log。

从这里看出,为了保持内存中树的平衡,维护btree+这样一个数据结构,须要作不少工做,性能会有必定下降,主要是插入,更新,删除都不是上边两种方式,经过append 文件,这里的插入,更新,删除,会查找到具体的内存位置,而后是磁盘位置,进行磁盘数据的更新,这回产生大量的随机IO, 写入,更新,删除,这类型的随机IO会更多。

流程

LSM和Btree因为实现原理的不一样,因此不过也都有各有优缺点,LSM因为顺序写,提供更快的写入性能,Btree因为更加平衡,读取性能会更强。

总结

虽然这些年层出不穷的出现了各类数据库,可是其实不少数据库底层的存储引擎仍是有不少类似性,能够看到各种存储引擎实现方式应用的场景都有必定的不一样,好比hash索引更适合简单的kv存储,适合作缓存,lsm适合高并发的数据写入,btree更适合大量查询的应用场景,因此在实际应用的时候,咱们须要考虑咱们的应用场景,若有大量查询的场景(OLTP),像电商,就更好使用btree引擎的数据库,如mysql, oracle。须要高并发写入的场景,如IOT设备写入,用户访问日志记录等,可使用LSM为基础实现的分布式nosql hbase,cassandra等。再而后对于简单数据操做,页面缓存,热点数据缓存,可使用hash结构的memcache,redis等。

最后,推荐看一下leveldbboltdb的源码,代码比较少,更容易数据库实现原理。

参考文献


designing data-intensive applications

大规模分布式存储系统

MySQL技术内幕:InnoDB存储引擎

bitcask-intro

https://github.com/google/leveldb

相关文章
相关标签/搜索