以MySQL数据库索引为例,参考文章为:MySQL索引背后的数据结构及原理
索引(Index)是帮助MySQL高效获取数据的数据结构。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
图1展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上并不是一定物理相邻的)。为了加快Col2的查找,可以维护以右边所示的二叉查找树,每个节点分辨包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找树在O(logn)的复杂度内 获取到相应数据。
目前大部分数据库系统及文件系统都采用B-Tree或其变种B-Tree作为索引结构。
B-Tree
1、d为大于1的正整数,称为B-Tree的度
2、h为一个正整数,称为B-Tree的高度
3、每个非叶节点由n-1个key和n个指针构成,其中d<=n<=2d
4、每个叶子节点最少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶节点的指针均为null。
5、所有叶节点具有相同的深度,等于树高h。
6、一个节点中的key从左到右非递减排列。
。。。。
一个度为d的B-Tree,设其索引N个key,则其树高h的上限为logd((N+1)/2),检索一个key,其查找节点个数的渐进复杂度为O(logdN)。
B+Tree
与B-Tree相比,B+Tree有以下不同点:
1、每个节点的指针上限为2d而不是2d+1
2、内节点不存储data,只存储key;叶子节点不存储指针
由于并不是所有节点都具有相同的域,因此B+Tree中叶节点和内节点一般大小不同。这点与B-Tree不同,虽然B-Tree中不同节点存放的key和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中B-Tree往往对每个节点申请同等大小的空间。
带有顺序访问指针的B+Tree
在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,解就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问性能,例如图4中如果要查找key为从18到49的所有数据记录,当找到18后,只需要顺着节点和指针遍历就可以一次性访问到所有数据节点,极大提高了区间查询效率。
为什么使用B-Tree(B+Tree)
一般来说,索引本身很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。这样的话,索引查找过程就要产生磁盘IO消耗,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘IO操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘IO的存取次数。
主存存取的时间与存取次数呈线性关系,因为不存在机械操作,两次存取的数据的“距离”不会对时间有任何影响。
为了提高效率,尽量减少磁盘IO,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样的理论依据是局部性原理。
B-/+Tree索引的性能分析
根据B-tree的定义,可知检索一次最多需要访问h个节点。利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一个IO就可以完全载入。每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里。
B-tree中一次检索最多需要h-1次IO(根节点常驻内存),渐进复杂度为O(h)=O(logdN),一般实际应用中,度d是非常大的数字,通常超过100,因此h非常小。
所以B-tree作为索引结构效率是非常高的。
而红黑树,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的IO渐进复杂度为O(h),效率明显比B-tree差很多。
B+tree更适合做索引,原因和内节点出度d有关。d越大,索引性能越好,而度d的上限取决于节点内key和data的大小。由于B+tree内节点丢掉了data域,因此可以拥有更大的度,拥有更好的性能。
MyISAM索引实现:
MyISAM引擎使用B+tree作为索引结构,叶节点data域存放的是数据记录的地址。
主索引和辅助索引结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。
因此,MyISAM中索引检索的算法为首先按照B+tree搜索算法搜索索引,如果指定的key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
MyISAM的索引方式也叫作“非聚集”的。
InnoDB索引实现
也使用B+tree作为索引结构,但实现方式与MyISAM不同。
1、InnoDB的数据文件本身就是索引文件。MyISAM索引文件和数据文件分离,索引文件仅保存数据记录的地址。而InnoDB中,表数据文件本身就是按B+tree组织的一个索引结构,这棵树的叶节点data保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录,这种索引叫作聚集索引。
因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有)。
2、与MyISAM索引不同的是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。
聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两边索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
InnoDB为什么不建议使用过长的字段作为主键?因为索引辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。
用非单调的字段作为主键在InnoDB中不是个好主意。因为InnoDB数据文件本身是一颗B+tree,非单调的主键会造成在插入新记录时数据文件为了维持B+tree的特性而频繁的分裂调整,十分低效,而使用自助字段作为主键是一个很好的选择。