本篇主要讨论的是不同存储结构(主要是LSM-tree和B-tree),它们应对的不同场景,所采用的底层存储结构,以及对应用以提升效率的索引。

所谓数据库,最基础的功能,就是保存数据,并且在需要的时候可以方便地检索到需要的数据。在这个基础上,演化出了不同的数据库系统,以及多种索引机制帮助检索数据。这篇我们就来讨论几种常见的数据存储和索引机制,主要是B-tree,LSM-Tree,以及它们对应的优缺点。

顺序存储与哈希索引

试想一下,如果按照保存数据,并且在需要的时候可以方便地检索到需要的数据这一标准,设计一个简单的数据库,那么最简单的做法应该怎么做呢?

最简单的做法,就是通过顺序保存数据到一个日志文件中。然后通过索引,这里以哈希索引为例(比如java的hashMap),记录每条数据的key以及对应的位移,将其保存到内存中,避免随机检索巨大的开销。值得注意的是,引入索引,虽然会显著提高查询效率,但会略微降低写入速度。因为每次写入的时候都需要额外写入到哈希索引中,这一点对大部分索引都是适用的。

上图为哈希索引示例,下面是顺序存储在磁盘上是日志数据,上面的内存中的哈希索引。哈希索引是很多复杂索引的基础,比如在mysql中就有提供哈希索引的选项,当然哈希索引并不常用,因为它最基础,同时也意味着它最容易被优化。

上述形式的顺序存储+哈希索引中,增加数据和查找数据相对容易理解,而修改数据则可以通过将新数据追加到文件尾部,重新生成索引实现,删除操作则可以给与哈希索引一个标识符实现(如对应key置为-1)。

但这样有一个问题,可能会出现磁盘耗尽的情况。针对这一个问题,我们可以将日志文件拆分成多个一定大小的文件段(这里的文件段可以理解为接受统一管理的数据文件)。当一个文件段达到一定大小,比如4kb的时候,就关闭它,新建一个文件段。而旧的文件段可以进行压缩,前面提到过,删除和修改都是通过追加日志相同的key-value实现的,那么早先的数据其实就已经没用的,所以压缩的时候只保留最新的key数据。压缩到过程如下面这张图所示:

图中上面的部分就是顺序存储的数据,可以发现其中有很多的key都是相同的,这是因为顺序存储情况下,修改数据就是不断新写入相同的key。这种情况我们要的只有相同key的最新的value。所以压缩过程也是一个清理磁盘的过程。

压缩合并过程可以由后台进行默默进行,所以不必担心这个过程影响查询性能。上图中只有一个数据文件段,但实际上可以有多个文件段,多个文件段也可以合并(类似于Hbase中多个文件的merge操作)。

当然这样的优化可以极大程度节省空间,但必不可少得会给检索带来时间上的损耗。在多个文件段的情况,每个文件段都有自己的哈希索引,故而要查找数据会首先根据key查找内存中最新文件段的哈希索引,如果找不到,那么找次新文件段的哈希索引,接着找次新的哈希索引,直到遍历所有文件段的哈希索引。

综上,顺序存储+哈希索引优点明显,简单,高效。缺点是哈希索引需全部存到内存(如果将哈希索引放到磁盘那相当于放弃了检索的高效),并且难以实现区域查询

为了解决它的这些问题,我们可以将哈希索引做一些小小的改变。具体来说,就是让文件段的数据,按key进行排序存储。这样会带来哪些改变呢?

SSTable和LSM tree

将数据文件段中的数据按key进行排序,并且保证相同的key只出现一次(在压缩的时候保证),这种格式就称之为排序字符串表,简称SStable(Sorted String Table)。

将数据按Key进行排序后有以下几个好处:

  1. 合并更加简单高效,即使数据文件段大于内存,也可以使用类似归并排序算法进行数据段的压缩,即将一个大文件拆成多个小数据进行压缩。如果多个文件段中有相同的key,那么以最新的文件段的key为准。
  2. 缓解哈希索引需要整个hashMap存储到内存的窘境。因为key是排序的,所以可以在内存中维持一个稀疏索引,存储每个key的范围,具体见下图。并且这个稀疏索引所需的内存空间是很小的。

通过稍微改变一下文件段的结果,就获得如此多的好处。但还有一个问题,前面的哈希索引是基于顺序存储的日志文件的,要让SStable按key排序,那就不能顺序存储磁盘了呀(即无法存储的时候立即写入磁盘)!!的确是这样,虽然也可以使用类似B-tree来实现磁盘上的排序存储,但转换下思路,其实将数据先保存在内存中其实更加方便

具体实现流程,是在内存中维护一个类似TreeMap的数据结构用于存储数据(TreeMap底层是基于红黑树对存储的key进行排序的。无论我们按照什么样的顺序存储数据,TreeMap总是会将数据按照key进行排序)。这个TreeMap称为内存表,当内存表超过一定阈值的时候,就将其写入到磁盘中,成为SStable,因为已经排好序,所以写入的效率其实比想象的要高。后期再对磁盘中的SStable进行压缩与合并操作。

当需要根据key检索的时候,会先去内存表中检索,找不到再去最新的SStable,再去次新的SStable,直到遍历完全部。

上述这种索引结构被称为之LSM-Tree,全称是Log-Structured Merge-Tree,即日志合并树。而这种基于合并和压缩文件原理的存储引擎被称为LSM存储引擎,其中比较为人所知的是Hbase。

B-Tree

最后,我们再来讨论流传最久的数据库村粗结构。与LSM-Tree这几年才逐渐为人所知不同,B-tree存储结构担得起经久不衰这四个字。

B-tree本身是一种树形的数据结构,更具体点说是一颗平衡查找树,它也是通过存储顺序的key存储数据(这一点和SStable有相似之处)。不同于前面的LSM-tree的文件段,B-tree将数据库分解成固定大小的块或页,通常一个页大小是4kb。这种分配方法更加贴合底层的磁盘。

当需要进行查找的时候,总是从根开始,根据范围跳转到对应的key,而其对应的value可以是值本身,也可以是指向存储对应数据的磁盘地址。下图是一个具体的例子:

而在更新或插入的时候,有可能会出现没有足够空间来容纳新key的问题,这时候就会发生分裂。分裂操作是比较危险的,在分裂的时候如果数据库崩溃,可能会导致索引被破坏。为了防止这个问题,可以引入预写日志(write-ahead log,WAL)机制。mysql的binlog就是这样的东西。具体说就是在执行操作的时候,将此次操作写入一个只允许追加的文件中,这样一来当崩溃的时候就可以检查日志并进行恢复。

存储结构的比对

从使用的角度上来说,B-tree等索引存储结构多用于OLTP型的数据库,因为这类数据库主要以事务,或是行级别的读取和存储为主的(比如Mysql)。换句话说,这种类型的数据库更多的操作是小批量或单行级别的更新或读取,并且可能还有事务方面的需求,这种类型正是B-tree结构所擅长的。

而 LSM-tree则多用于大规模数据情况下的检索分析和快速写入的情况。在写入的性能上,因为上直接写入内存再定期刷入到磁盘中,所以写入操作对用户的感知而言上非常迅速的。而检索速度也因为key顺序存储,可以快速定位到key对应的位置,因而具有较好的检索性能。

但是LSM-tree比较显著的应用方向还是在大规模分析这方面,在大规模分析(OLAP)场景下,数据通常都是列式存储,并且需要全表扫描。其中磁盘数据可以使用二进制进行压缩,读取的时候可以有效减少磁盘IO的处理时间(与之相比,B-tree等存储结构就无法充分压缩,因为每次都只处理小部分数据)。同时在存储文件中还能再进一步切分,比如将列式数据按照水平切分成不同的Page,同时存储一些简单的索引,用来指定不同Page大概范围,Hadoop的存储数据格式Parquet就是类似的设计。

小结

本篇主要讨论了几种基础的存储结构和索引以及其对应使用场景,限于篇幅,更多索引的变种无法多加讨论,比如B-tree的优化版B+tree,多列索引等。

其实大部分数据库或者说存储引擎,都是针对不同的场景下,在旧有的基础上进行一定程度的微改造创新,但大体的结构依旧是以上述两三种为准,了解了上述几种结构,对数据存储方面应该能够有一个感性的认知了。

此外本章多参考自《DDIA》第三章节,对分布式系统感兴趣的童鞋可以看看此书,肯定不会失望的。

以上~

数据的存储结构浅析LSM-Tree和B-tree的更多相关文章

  1. 【SQL SERVER重新认识】数据内部存储结构简单探索

    数据库经常需要打交道,但是从来没想过数据库内部是如何存储数据. 今天探索一下数据库内部如何存储数据,从下面几个方面探索 数据库内部如何存储数据 索引数据如何存储 操作数据对存储影响 总结 数据库内部如 ...

  2. SQL SERVER大话存储结构(3)_数据行的行结构

            一行数据是如何来存储的呢?     变长列与定长列,NULL与NOT NULL,实际是如何整理存放到 8k的数据页上呢?     对表格进行增减列,修改长度,添加默认值等DDL SQL ...

  3. oracle中的rowid和数据行的结构

    在oracle数据库系统中每一行都有一个rowid,oracle数据库系统就是利用rowid来定位数据行的.rowid也是oracle中内置的一个标量数据类型 rowid有一下特点; 是数据库中每一行 ...

  4. Oracle 体系结构四 逻辑和物理存储结构之间的关系

    Oracle数据库从物理存储中完全抽象出逻辑存储.逻辑数据存储采用“段”的形式.段的类型有很多种:典型的段是“表”.这些段以物理形式存储在数据文件中.通过表空间将逻辑存储从物理存储中抽象出来.逻辑结构 ...

  5. B-Tree和 B+Tree的数据存储结构

    B+树索引是B+树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引.B+树中的B代表平衡(balance),而不是二叉(binary),因为B+树是从最早的平衡二叉树演化而来的.在讲B ...

  6. Atitit.数据索引 的种类以及原理实现机制 索引常用的存储结构

    Atitit.数据索引 的种类以及原理实现机制 索引常用的存储结构 1. 索引的分类1 1.1. 按照存储结构划分btree,hash,bitmap,fulltext1 1.2. 索引的类型  按查找 ...

  7. Atitit.数据索引 的种类以及原理实现机制 索引常用的存储结构

    Atitit.数据索引 的种类以及原理实现机制 索引常用的存储结构 1. 索引的分类1 1.1. 索引的类型  按查找方式分,两种,分块索引 vs编号索引1 1.2. 按索引与数据的查找顺序可分为 正 ...

  8. 0809MySQL实战系列:大字段如何优化|数据存储结构

    转自https://yq.aliyun.com/articles/59256?spm=5176.100239.blogcont59257.9.5MLR2d 摘要: 背景 线上发现一张表,1亿的数据量, ...

  9. 联合索引在B+树上的存储结构及数据查找方式

    能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 引言 上一篇文章<MySQL索引那些事>主要讲了MySQL索引的底层原理,且对比了B ...

随机推荐

  1. SunOS下root账户无法执行crontab问题

    SunOS下root账户无法执行crontab问题   直接步入正题,处理方法如下: 1.查看可执行crontab的用户: more /etc/cron.d/cron.deny 2.修改crontab ...

  2. 题解 洛谷P2959 【[USACO09OCT]悠闲漫步The Leisurely Stroll】

    原题:洛谷P2959 不得不说这道题的图有点吓人,但实际上很多都没有用 通过题上说的“三岔路口”(对于每一个节点有三条连接,其中一条连接父节点,另外两条连接子节点)和数据,可以那些乱七八糟的路和牧场看 ...

  3. Python中的时间与日期

    本文简要介绍datetime,time模块的简要用法. datetime模块 datetime模块主要有四个主要的对象. date 处理年.月.日 time处理时.分.秒.微秒 datetime处理日 ...

  4. PAT 1028 List Sorting (25分) 用char[],不要用string

    题目 Excel can sort records according to any column. Now you are supposed to imitate this function. In ...

  5. zsy后台管理系统-架构设计

    Zsy框架总体架构设计 1.Mysql数据库,存储所有表的数据. 2.Zsy-基础项目(Zsy-Model,Zsy-Dao,Zsy-Service,Zsy-Web),基于SSM框架.项目功能包含基本的 ...

  6. webpack指南(四)shimming

    shimming 将一个新的 API 引入到一个旧的环境中,而且仅靠旧的环境中已有的手段实现. ProvidePlugin 我们在程序中暴露一个变量,通知webpack某个库被使用,webpack将在 ...

  7. js性能优化之---防抖函数

    使用场景 input的时时触发搜索功能 scroll事件的滚动条位置的监测 resize事件监听窗口变化等 举个栗子(input框时时触发搜索功能) 普通未防抖款 var textElement = ...

  8. ConcurrentHashMap.Segment源码解析

    ConcurrentHashMap通过将完整的表分成若干个segment的方式实现锁分离,每个segment都是一个独立的线程安全的Hash表,当需要操作数据时,HashMap通过Key的hash值和 ...

  9. poj2823单调队列认知

    Sliding Window Time Limit: 12000MS   Memory Limit: 65536K Total Submissions: 62930   Accepted: 17963 ...

  10. 四、HTML属性—— HTML 元素提供的附加信息

    HTML属性 (1)属性一般描述于开始标签 (2)属性总是以名称/值对的形式出现,比如:name="value" (3)使用小写属性 HTML属性值 应该始终被包括在引号内. —— ...