最近工作中遇到了一些索引的问题,发现自己其实并不了解,因此稍微了解下。在介绍B+树之前,需要先了解下B树,部分信息来源自参考文档。
什么是 B 树
B树概念
B树也称B-树,它是一棵多路平衡查找树(和二路对应)。二叉树我想大家都不陌生,其实,B树和后面讲到的B+树也是从最简单的二叉树变换而来,下面我们来看看B树的定义。我们定义m表示树的阶。阶数表示了一个节点最多有多少个子节点,那么一棵B需要满足以下几个条件
1.每个节点最多有m-1个关键key(可以存有的键值)。 2.根节点最少可以只有1个关键字。意思是也可以有多个。 3.非根节点至少有m/2个关键字。如果少了,那么就要进行树的调整 4.为了平衡查找,每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。这个很简单了。没有这个保证的话,平衡查找无从谈起。 5.所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。 6.包括非叶节点在内,每个节点都存有key和数据,也就是对应的key和value。
也就是说,根节点的关键字数量k的范围:1 <= k <= m-1,非根节点的关键字数量范围:m/2 <= k <= m-1。
什么是B+树
B+树其实和B树是很相似的,特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。
相同点
1.根节点至少一个元素 2.非根节点元素范围:m/2 <= k <= m-1
不同点
1.B+树有两种类型的节点:内部结点(也称索引结点)和叶子结点。内部节点就是非叶子节点,内部节点不存储数据,只存储索引,数据都存储在叶子节点。而B树都存储数据,这会导致查询性能不稳定,因为查找次数不确定。
2.内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。 3.每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。 4.父节点存有右孩子的第一个元素的索引。
mysql中的选择
索引的数据结构
数据库中,数据都存在磁盘中,索引也多到大部分存在磁盘中,这样对于索引,每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。就这样,b+树应运而生。
MySQL 默认的存储引擎选择 B+ 树而不是哈希或者 B 树的原因:
1.哈希虽然能够提供 O(1) 的单数据行操作性能,但是对于范围查询和排序却无法很好地支持,最终导致全表扫描; 2.B 树能够在非叶节点中存储数据,但是这也导致在查询连续数据时可能会带来更多的随机 I/O,而 B+ 树的所有叶节点可以通过指针相互连接,能够减少顺序遍历时产生的额外随机 I/O;
如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
b+树的查找过程
如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
b+树和索引的关系
联合索引,如果有一个3列索引(name,age,sex),则已经对(name)、(name,age)、(name,age,sex)上建立了索引;
1.我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
2.当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
参考
https://zh.wikipedia.org/wiki/B%2B%E6%A0%91
https://segmentfault.com/a/1190000020416577