数据存储-索引结构

来源:互联网 发布:金石软件 编辑:程序博客网 时间:2024/05/16 02:05

B树(B-Tree)的由来、数据结构、基本操作以及数据库索引的应用

B树是为磁盘存储而专门设计的一类平衡搜索树,B树的高度仅随着它所包含的节点数按对数增长,不过因为单个节点可以包含多个关键字,所以对数的底数可以比较大,实际应用中一般是50~2000,给个直观的数字,一棵分支因子为1001、高度为2(不包含根节点)的B树,可以存储超过10亿个关键字!

1.从磁盘结构讲起

计算机的机械磁盘,为了摊还机械移动花费的等待时间,磁盘会一次存取多个数据项而不是一个,这样的一次读取的信息单元是page,我们可以用读或写的页数作为磁盘存取总时间的主要近似值,在任何时刻,B树算法都只需在内存中保持一定数量的页面。B树的设计考虑磁盘预读取这点,一个B树的节点通常和一个完整磁盘页(page)一样大,并且磁盘页的大小限制了一个B树节点可以含有的孩子个数(分支因子),当然这个具体也需要取决于一个关键字相对一页的大小。B+ Tree中内部节点只存放关键字和孩子的指针,不存其他satellite information,因此最大化了内部节点的分支因子。

2.B树的数据结构

复制代码
typedef int KeyType;
#define m 3
struct
Node{ int keynum; /* 结点中关键字的个数,即结点的大小*/ struct Node *parent; /*指向parent结点*/ KeyType key[m]; /*关键字向量*/ struct Node *ptr[m]; /*子树指针向量*/ };
复制代码

3.B树的查找

搜索一棵B树和搜索一棵二叉搜索树很相似,只是在每个节点所做的不是二叉或者“两路”分支选择,而是根据根节点的孩子数做多路分支选择。

4.B树的插入

B树插入的时候都是插入到叶节点上,插入的时候会从根节点开始顺着叶节点的方向沿途,如果遇到一个满节点(该节点上的关键字达到2t-1,t代表t阶B树),就会split该节点,分裂节点方式就是把满节点上的中间关键字往根节点方向提,分裂是树长高的唯一途径。B树的每个叶节点具有相同的高度,所以B树高度的增加发生在顶部而不是底部。插入节点的时候,从根的方向往下判断,如果不是叶子节点,则必须选择适当的叶子节点插入,因为在沿途已经分裂了节点,所以保证不会在满节点上再插入节点。

5.B树的删除

和插入关键字类似,插入关键字的时候要保证节点不会太大,而且有可能会增高B树。删除节点的时候要保证一个节点不会变得太小,因为B树的节点上的关键字有下界要求(除了根节点以外的每个内部节点至少有t个孩子,如果树非空,根节点上至少有一个关键字),删除关键字的时候如果在叶子节点,而且删除之后还满足B树的要求,那直接删除即可,不过如果是其他情况,比如在内部节点上删除关键字,那就有一系列的算法分支需要考虑,感兴趣的读者可以自行找资料慢慢琢磨了。不过在实际场景中,由于一棵B树中大部分关键字都在叶节点中,删除操作最经常是从叶子节点中删除关键字。

6.B树的应用场景 

mysql的MyISAM和InnoDB两个存储引擎的索引实现方式:

  • MyISAM引擎使用B+ Tree作为索引结构,叶节点存放的是数据记录的地址。
  • MyISAM引擎的辅助索引(二级索引)和主索引在结构上没有区别,只是辅助索引的key可以重复,叶节点上存放的也是数据记录的地址。
  • MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
  • InnoDB中表数据本身就是按B+ Tree组织的一个索引结构,叶节点存放的就不是数据记录的地址,而是完整的数据记录。所以InnoDB这种存储方式,又称为聚集索引,使得按主键的搜索十分高效,但二级索引搜索需要检索两遍索引:首先二级索引获得主键,然后用主键到主索引中检索到数据记录。
  • 因为主键是InnoDB表记录的”逻辑地址“,所以InnoDB要求表必须有主键,MyISAM可以没有。

 


LSM树由来、设计思想以及应用到HBase的索引

讲LSM树之前,需要提下三种基本的存储引擎,这样才能清楚LSM树的由来

  • 哈希存储引擎  是哈希表的持久化实现,支持增、删、改以及随机读取操作,但不支持顺序扫描,对应的存储系统为key-value存储系统。对于key-value的插入以及查询,哈希表的复杂度都是O(1),明显比树的操作O(n)快,如果不需要有序的遍历数据,哈希表就是your Mr.Right
  • B树存储引擎是B树的持久化实现,不仅支持单条记录的增、删、读、改操作,还支持顺序扫描(B+树的叶子节点之间的指针),对应的存储系统就是关系数据库(Mysql等)。
  • LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎一样,同样支持增、删、读、改、顺序扫描操作。而且通过批量存储技术规避磁盘随机写入问题。当然凡事有利有弊,LSM树和B+树相比,LSM树牺牲了部分读性能,用来大幅提高写性能。

通过以上的分析,应该知道LSM树的由来了,LSM树的设计思想非常朴素:将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操作批量写入磁盘,不过读取的时候稍微麻烦,需要合并磁盘中历史数据和内存中最近修改操作,所以写入性能大大提升,读取时可能需要先看是否命中内存,否则需要访问较多的磁盘文件。极端的说,基于LSM树实现的HBase的写性能比Mysql高了一个数量级,读性能低了一个数量级。

LSM树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。

 

以上这些大概就是HBase存储的设计主要思想,这里分别对应说明下:

  • 因为小树先写到内存中,为了防止内存数据丢失,写内存的同时需要暂时持久化到磁盘,对应了HBase的MemStore和HLog
  • MemStore上的树达到一定大小之后,需要flush到HRegion磁盘中(一般是Hadoop DataNode),这样MemStore就变成了DataNode上的磁盘文件StoreFile,定期HRegionServer对DataNode的数据做merge操作,彻底删除无效空间,多棵小树在这个时机合并成大树,来增强读性能。

 

关于LSM Tree,对于最简单的二层LSM Tree而言,内存中的数据和磁盘你中的数据merge操作,如下图

图来自lsm论文

lsm tree,理论上,可以是内存中树的一部分和磁盘中第一层树做merge,对于磁盘中的树直接做update操作有可能会破坏物理block的连续性,但是实际应用中,一般lsm有多层,当磁盘中的小树合并成一个大树的时候,可以重新排好顺序,使得block连续,优化读性能。

hbase在实现中,是把整个内存在一定阈值后,flush到disk中,形成一个file,这个file的存储也就是一个小的B+树,因为hbase一般是部署在hdfs上,hdfs不支持对文件的update操作,所以hbase这么整体内存flush,而不是和磁盘中的小树merge update,这个设计也就能讲通了。内存flush到磁盘上的小树,定期也会合并成一个大树。整体上hbase就是用了lsm tree的思路。

 

0 0
原创粉丝点击