这是我参与「第三届青训营 -后端场」笔记创作活动的第 3 篇笔记
B 树
参考 B树 - wikipedia.org ,一颗高度为 2 的 B 树长这样:
其特点如下:
- 每个节点的容量是确定的,并且是顺序存储(为了支持二分查找)
- 每个节点含有多个键,且是有序的
- 键在树中不重复出现
- 每个节点的每个键既存子节点的指针,也存键所对应的数据
- 数据没有被链接起来
B+ 树
参考 B+树 - wikipedia.org ,一颗高度为 2 的 B+ 树长这样:
其特点如下:
- 每个节点的容量是确定的,并且是顺序存储(为了支持二分查找)
- 每个节点含有多个键,且是有序的
- 键可能在树中重复出现
- 每个键都有一个指针指向它的子节点
- 数据存储在叶子节点,叶子节点是有序链表(可以是单向或双向)
为了更好地理解 B+ 树,建议是从叶子节点开始往根节点看,从叶子节点到根节点其实是一个不断把数量级成倍减少的过程。比如,最底层都是叶子节点,储存的是实际的数据,在上面加一层,如果是按最底层的每 512 个键分为一个块链接到新的这一层,每块链接一个键,那么新的这一层的键的个数就是 数据个数 除以 512,如此反复,不需要多少层就能构建出有一个根节点的 B+ 树。然后查找只需从根节点读取一个节点的数据,利用二分查找快速定位到下一个节点,如此反复,直到查找到最底层的叶子节点。可以简单认为 B+ 树查找的时间复杂度是 O(log n) , n 为树中的数据个数。
MySQL 中的 B+ 树
B+ 树是一颗多路平衡查找树,所有节点称为页,页就是一个数据块,里面可以放数据,页是固定大小的,在 InnoDB 中是 16KB。
查看官方文档 dev.mysql.com/doc/interna… 举的一个例子,可以知道 B 树和 B+ 树最大的区别就是 B+ 树的叶子节点是链接的,MySQL 使用的是双向的链接(下图只是举例,不是说 MySQL 的 B+ 树是二叉的)。
--------
- root -
--------
|
----------------------
| |
| |
-------- --------
- leaf - <--> - leaf -
-------- --------
一般节点的大小会设置为磁盘页的大小,并且会存储为有一个根节点的 B+ 树,这样能减少磁盘 IO 次数。
页大小通过 innodb_page_size 配置,只能在实例化前配置,实例化后无法更改。
MyISAM 和 InnoDB 的索引都是 B+ 树,MyISAM 只支持非聚集索引,InnoDB 聚集索引和非聚集索引都支持。 聚集索引和非聚集索引的区别就是,聚集索引 B+ 树的叶子节点储存了实际数据,而非聚集索引存储的是数据的指针。MySQL 的主键索引是聚集索引;MySQL 的辅助索引(也就是非主键索引)是非聚集索引,叶子节点存储的是主键;
B+ 树的插入和删除简单来说,都是从根节点开始,递归地查找目标区间:
- 删除操作:递归地查找目标区间直到叶子节点时把数据赋值为空,若中途找不到目标区间就直接结束;
- 插入操作:递归地查找目标区间直到叶子节点时把数据储存进叶子节点,若中途找不到目标区间就要建立目标区间并继续递归,最后如果目标叶子节点是新建的,还要将该叶子节点链接到叶子节点链表;
MySQL 的 B+ 数索引、hash 索引、full-text 索引
full-text 索引也叫全文索引。 三个引擎都支持 B+ 树索引; 全文索引 InnoDB 和 MyISAM 都支持,从 MySQL 5.6 版本开始 InnoDB 支持全文索引,InnoDB 旧的版本不支持全文索引; hash 索引只有 memory 存储引擎支持(InnoDB 也支持只不过是自动创建的,不能人为地创建);
总结
总的来说,B+ 树有以下优点(优点是相对而言的,具体情况还要具体分析):
- 节点存储的键多,树的高度就小,能减少磁盘 IO;
- 叶子节点是链接的,适合范围查找,同时在范围查找时也能减少磁盘 IO;
- 稳定的 O(log n) 时间复杂度;
- 遍历只需遍历叶子节点链表,空间复杂度 O(1) ;
1. B 树和 B+ 树的区别?
- B+ 树的叶子节点是有序链表,而 B 树的数据没有被链接
- B+ 树数据存储在叶子节点,而 B 树的数据存储在各个节点
- 键在 B+ 树中可能重复出现,而在 B 树中只出现一次
2. MySQL 使用 B+ 树而不使用 B 树的原因?
- B 树的每个键不只存储子节点指针,还要实际存储数据,这样每个节点能储存键的个数会少很多
- MySQL 使用 B+ 树是因为 B+ 树能把数据有序链接起来,方便查找一个范围内的数据。
B 树想把存储数据的键有序链接起来比较困难,因为这些键分散在树的不同的层级。B 树有一个优点,如果在根节点就找到目标键,那直接就能读到数据,不需要像 B+ 树那样还要搜索到叶子节点才能读到数据。
3. 为什么 B+ 树更适合磁盘存储?
- 查找一个数据的 IO 次数最差情况下等于树的高度,相比于平衡二叉树和红黑树,B+ 树节点键的数量多所以高度更小,能减少磁盘 IO;
- 叶子节点是有序链接的,在范围查找时,无需重新从根节点查找,同样能减少磁盘 IO;
4. InnoDB 的索引最多能存多少条记录?
InnoDB 的索引使用的是 B+ 树,理论上能存的记录条数没有上限,但是如果记录条数特别多,树的高度就会很高,这样会影响查找的性能。InnoDB 的页大小为 16KB (包含文件头、页头、页尾等结构,并不是全部都用来存储键和指针),这样的 B+ 树,如果有三层,能够存储超过 2100 万条记录。我们可以根据这个来确定是否要分库分表来保证树的高度在可接受的范围。
5. 计算三层 MySQL 的 B+ 树能存储多少条记录?
数据页 16K 是一个包含文件头/页头/页尾等结构的数据页,这里我们只是估算,所以把 16KB 都当成只存储键和指针。假设一行数据大小为 1K 左右,主键 ID 为 bigint 类型,长度为 8 字节,而指针大小在 InnoDB 源码中为 6 字节,这样一共 14 字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针,即 16384/14=1170。所以我们每个非叶子结点最多有 1170 个子节点。 那么可以算出一棵高度为 2 的 B+ 树,能存放 117016=18720 条这样的数据记录。 根据同样的原理我们可以算出一个高度为 3 的 B+ 树可以存放: 1170117016=21902400 (2100万)条这样的记录。那如果四层呢:那就是 11701170117016=256 亿。大部分的 InnoDB 的 B+ 树都是 3 到 4 层,3层的性能会更好。
6. 为什么推荐使用自增 ID 做主键?
- 如果主键是自增 ID,那么数据的插入只需在叶子节点链表末尾插入。
- 如果主键不是自增 ID,为了保持 B+ 树的有序,会造成频繁的页分裂和页旋转,插入速度比较慢。
7. 为什么主键要尽量小?
MySQL 使用的是 B+ 树做索引,如果键变大,那每页能存储的键就更少,树的高度就会更高,磁盘 IO 增多;磁盘空间占用额外增多,B+ 树每个节点都存储了键,如果键的大小增加 1 ,实际增加的磁盘占超过 1 ;