mysql、postgresql、hbase、tidb数据库存储方案对比
MySQL Innodb | PostgreSql | HBase | TiDB | |
存储模型 |
1、采用page + buffer pool + redo log的方案,内存中的buffer pool缓存 了磁盘中的页面,通过哈希表判断访问的页是否在内存中,buffer pool中还维 护了一个页面的LRU链表,分为new和old两个区域,同时还维护了free list和 flush list两个链表,分别用于存放空闲页和脏页,脏页同时存在于LRU和flush list中。 2、redo log放在重做日志缓冲中,事务提交时,只需其对应的redo log被写 入磁盘即可,不要求脏页刷盘,由于redo log是顺序写,脏页刷盘是随机写, 因此提高了写入性能。每条redo log都有一个lsn号,页面中也保存了最新lsn, 重做时根据lsn判断是否应用redo log。flush list中的脏页和redo log逐一对 应。 3、采用undo log机制,数据更新时,页面保存最新版数据,旧数据放入undo log中; 4、采用checkpoint机制,将脏页定时写入磁盘(double write),脏页写入 磁盘后,其redo log就不再需要,因此最近一次checkpoint前的redo log可 以被覆盖重用。 5、采用change buffer机制,将对那些不在buffer pool中的页面的操作保存 下来,等change buffer满或有select查询该页时,再将这些操作merge到 buffer pool中的页面上。 |
1、采用page + buffer pool + redo log的方案,内存中的buffer pool缓存 了磁盘中的页面,通过哈希表判断访问的页是否在内存中,采用clock-sweep 算法来进行缓存页的淘汰。使用free list管理空闲页面,buffer pool使用动态 的内存管理方式,可以根据实际需要增加或减少。
2、采用redo log机制,事务提交时只需其对应的redo log被写入磁盘即可,
不要求脏页刷盘,由于redo log是顺序写,脏页刷盘是随机写,因此提高了
写入性能。根据lsn号判断是否需要应用redo log。
3、没有undo日志,mvcc的旧数据原地残留在数据页中,新数据在heap中 重新分配,有HOT(heap only tuple)机制,即旧数据指向新数据,索引指向 记录的指针不变,通过旧数据找到新数据;若非HOT场景,则更新数据需要 更新所有索引,即使没有更新索引字段。有vacuum进程定期去释放不再使 用的行和索引key占用的空间,vacuum后台进程对IO资源占用比较多,会导 致TPS和延时不稳定。 4、采用checkoutpoint机制,将脏页按一定触发条件写入磁盘。会遍历 buffer pool中所有的数据缓存页,然后将脏页取出并按照其对应的磁盘位 置进行排序,然后依次刷新到磁盘,脏页刷盘后wal日志就可以被删除或者 回收。
|
1、采用基于key-value的列存储方案,记录中无需保存所有字段,只需保存不为null的字段。 2、采用类似lsm-tree的存储方案,对数据的增删改通过log的方式顺序存储在内存MemStore中(使用跳表数据结构),当 MemStore中的数据量达到一定阈值时,数据会被刷写到磁盘中,即为HFile文件,MemStore刷盘的过程不阻塞系统的读写。后台线程按一定规则将小HFile文件合并为大文件,有minor合并和major压缩合并两种方式,minor将多个小文件合并为少数的大文件,major将若干个HFile重写为一个新HFile。 3、自动分区,HBase中扩展和负载均衡的基本单元为region,当region的大小达到一定阈值时,会分裂成两个新的region,行数据根据RowKey的范围可以分为多个region,region由region server管理,每个region server负责管理多个region,可以通过动态增加region server的数量来动态扩容。若其中一台region server宕机,其负责的region会被转移到其他region server上,通过WAL日志来恢复数据。 4、数据存储在HFile中, HFile存储在HDFS中。HFile中存储的是经过排序的键值映射结构,且键值是连续递增的,因此无需为新插入的键预留位置。 5、具有WAL日志(HLog),HBase 的写入主要分三步:1.先写 WAL 日志;2.再写入 memstore;3.最后写 Sync wal, 当region server宕机,可通过HLog恢复出MemStore中尚未刷盘的数据。 6、使用Zookeeper管理集群,只有一个主节点,主节点负责region管理和迁移,负责全局region的负载均衡,还负责元数据管理。 7、使用布隆过滤器,它保存在HFile的metaData中,使用布隆过滤器可以判断数据是否在某个数据块中,减少HFile中数据块的无效访问。 8、Region里边会有多个Store,每个Store其实就是一个列族的数据(所以我们可以说HBase是基于列族存储的),Store里边有MemStore和HFile。
|
1、TiDB是HTAP数据库,分为TiKV和TiFlush两个存储引擎,分别储存OLTP和OLAP类型的数据。TiKV底层使用RocksDB来持久化KV数据,TiKV管理数据的逻辑分片(region),将实际对数据的操作发给RocksDB处理。 2、采用基于key-value的列存储方案,每行数据按如下形式编码: 每一行记录对应一个RowID,对于行ID,TiDB 做了一个小优化,如果某个表有整数型的主键,TiDB 会使用主键的值当做这一行数据的行 ID。 3、每行数据有唯一的RowKey,RowKey包含了表Id和行Id,这样就确保了同一张表的所有数据放在一起,同一行中的所有列放在一起。 4、将 SQL 查询映射为对 KV 的查询,再通过 KV 接口获取对应的数据,最后执行各种计算。 5、每一个TiKV实例都管理了若干region,region中包含了一定范围RowKey的行数据,TiDB集群中有若干TiKV实例,region在这些实例中相互备份,使用raft协议进行同步数据,在每一个TiKV实例中,region管理的数据在底层使用RocksDB来实现KV数据的持久化。 6、具有TiFlush OLAP存储引擎,TiFlush中的数据需要从TiKV中同步过来。由于TiKV 本身没有 Table 的概念,TiDB 需要将 Table 数据按照 Schema 编码为 Key-Value 的形式后写入相应TiKV Region,然后通过 Multi-Raft 协议同步到 TiFlash,再由 TiFlash 根据 Schema 进行解码拆列和持久化等操作。 |
索引方案 |
1、主键采用B+树,主键即数据,叶子节点存储数据本身; 2、采用稀疏索引,只能索引到数据所在页面,需要在页面中通过二分法查找 到具体数据,数据在页面中逻辑上依主键升序排列,物理上无序; 3、主键B+树中,数据页面按照索引字段顺序连接在一个双向链表中,但在 磁盘中可能不是顺序分布; 4、还支持自适应hash索引,以及全文索引。 |
1、数据行在heap中存储,在逻辑上和物理上都没有任何特定顺序,b树 索引采用nbtree。 2、支持b树索引,哈希索引,倒排索引等。 |
1、支持辅助索引,实现方式是在HBase中创建一个新的表,该表的Rowkey是辅助索引的值,值为原表的Rowkey。 2、hbase的索引有:1、全局索引;2、覆盖索引;3、本地索引。全局索引只能查询row key中包含的字段,用前缀过滤,否则索引表不会生效,如果查询语句中的条件字段或返回字段不是索引字段,就会触发全表扫描。 |
1、TiDB 同时支持主键和二级索引(包括唯一索引和非唯一索引)。与表数据映射方案类似,TiDB 为表中每个索引分配了一个索引 ID,用 2、无论是表数据还是索引数据的 Key 编码方案,一个表内所有的行都有相同的 Key 前缀,一个索引的所有数据也都有相同的前缀。这样具有相同的前缀的数据,在 TiKV 的 Key 空间内,是排列在一起的。因此只要小心地设计后缀部分的编码方案,保证编码前和编码后的比较关系不变,就可以将表数据或者索引数据有序地保存在 TiKV 中。采用这种编码后,一个表的所有行数据会按照
|
事务方案 |
1、 支持读已提交、可重复读、串行化隔离级别,默认可重复读级别; 2、可重复读级别通过间隙锁防止幻读的发生,幻读:由于其他事务的提交,当 前事务的前置条件已不再满足,再次使用前置条件查询时得到的是新的结果; 3、通过redo log实现持久性,通过undo log实现原子性,通过锁实现一致 性,通过mvcc实现隔离性;undo log和数据页一样也会产生redo log,也会 被刷脏,故障恢复时会由redo log恢复出undo log,然后通过undo log将事 务未提交但已刷脏的页回滚。 4、通过活跃事务快照和记录中的事务id来判断的可见性,读已提交级别是每 次查询时生成一个快照,可重复读是在第一次查询时生成快照,后面查询不 再生成。 5、取消事务时,需要应用undo log中的日志,将数据恢复。 |
1、支持读已提交、可重复读、串行化隔离级别,默认可重复读级别。 2、mvcc的新旧数据都保存在heap中,通过活跃事务快照、clog、xmin和 xmax判断记录的可见性。读已提交级别是每次查询时生成一个快照,可重复 读是在第一次查询时生成快照,后面查询不再生成。 3、事务回滚时,只需将该事务在clog中的状态置为abort即可,不用回滚数据 页的修改,由后台线程回收旧数据。
|
1、仅支持行级事务,且目前只支持单行级别的 read uncommitted 和 read committed 隔离级别。 2、采用MVCC机制,HBase的增删改记录都有「版本」,默认以时间戳的方式实现。如果查询时没有指定版本号,默认返回最新数据。 3、写写并发控制,在写入(或更新)之前先获取行锁,写入完成后释放。支持批量写入(或批量更新),使用两阶段锁协议,首先获取所有待写入(更新)行记录的行锁,写入完成之后再统一释放所有行记录的行锁。 4、读写并发控制,利用数据的多版本,当读请求到来时,若读取的行正在被另一个事务改写,则该读请求将读取改行数据的历史版本。 |
|
缺点 |
1、当有大量数据插入或修改时,索引节点会频繁拆分和合并,将会导致大量的随机 磁盘IO,影响写入性能,不适合OLAP场景。 2、checkpoint刷脏机制,当redo log或buffer pool满了,会停止接受插入和 更新,就要根据LRU算法将脏页面刷盘,这时会导致随机IO。 3、采用undo段设计,回滚时需要重做每一个undo log,回滚较慢。 4、长事务会使undo log无法及时释放,占用大量空间,且老事务需要遍历整个 版本链才能找到需要的版本。 |
1、更新数据时,需要将旧数据copy一份,容易引起数据页膨胀。 2、不支持cluster索引,即主键相邻的两条记录在磁盘上也是相邻的,无法 享受到cluster 索引带来的性能优势。 3、在快照隔离级别和串行化隔离级别下,两个事务更新同一行,则只有一个 事务可以成功,另一个事务阻塞,并待第一个事务提交后回滚。当大量事务 更新热点行时,会造成大量事务回滚。
|
1、读数据性能较慢,需要依次遍历HFile直到找到所需数据。 2、需要后台线程不断合并增量数据,合并的频率和数量可能会影响磁盘IO。 |
1、RocksDB 存在着数十倍的写入放大效应。在写入量大的应用场景中,这种放大效可能会触发 IO 带宽和 CPU 计算能力的瓶颈影响在线写入性能。除了写入性能之外,写入放大还会放大 SSD 盘的写入磨损,影响 SSD 盘的寿命。不过TiDB基于RocksDB 自研的KV存储引擎Titan部分缓解了这个问题。 |
优点 |
1、读写性能比较均衡。 2、修改元组时不需要拷贝整行,只需要将被修改列的旧值存入回滚段。 3、垃圾回收的影响更小,清理旧版本不涉及数据页,只需要将最老活跃事务之 前的事务undo log清除。
|
1、heap表插入、修改和删除数据采用追加的方式,速度较快,适合OLAP 场景。 2、事务回滚速度较快,只需要将事务在clog中的状态置为abort即可。 3、修改数据时,无需修改索引,因为索引指向数据在页面中的逻辑指针, 逻辑指针才指向数据的物理地址,所以只用修改页面内逻辑指针的指向即可。
|
1、使用日志文件和内存将随机写转换成顺序写,能保证稳定的数据插入速率,即以磁盘传输速率工作。 2、存储数据的布局较优,查询一个键需要的磁盘寻道次数的成本是透明的:假如有5个存储文件,则一个访问最多需要5次磁盘寻道。 |
1、纯分布式架构,拥有良好的扩展性 |
总结:
数据库由计算引擎 + 存储引擎组成,一款数据库是什么类型(如关系型、KV、图数据库等)不取决于存储引擎,而是计算引擎决定的,比如关系型数据库的数据可以用KV形式保存(TiDB,底层用RocksDb存储,采用LSM机制更新),也可以用B+树保存(innodb),甚至如果不考虑列拼接的性能也可以使用列式存储;图数据库可以采用图原生的设计,也可以基于传统关系型存储,使用点表和边表保存数据。计算引擎将数据从这些存储引擎中读出来后,按业务场景进行SQL计算或NoSQL计算,从而成为了关系型数据、图数据库、KV、向量、时序等,而某种存储引擎可能在某种场景下能工作的更好,比如行式存储引擎不适合olap场景。因此有些数据库只是在基于已有的存储方案上,封装了一层计算层中间件,而有些数据库则是将其他数据库的计算层和另外的数据库的存储层进行组合。