如果把数据库中的数据当做1个词典,那索引就是字典的目录,其目的是提升查找数据的速度。

树的数据结构天然适合查找操作,最先被想到就是搜索二叉树。

搜索二叉树

二叉树(Binary Search Tree)是每个节点最多有2个子树(左子树和右子树)的树结构,而搜索二叉树是一类特殊的二叉树,其具有以下性质:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值;
  • 它的左右子树也分别为搜索二叉树。

搜索二叉树中序遍历的结果是有序的。

搜索二叉树能够提高查询的效率 O(logN),但是当你插入 {1, 2, 3, 4, 5, 6} 这类数据的时候,搜索二叉树会退化成"链表",搜索效率变为 O(N)。

为了让二叉搜索树的左右子树平衡,出现了平衡二叉树。

平衡二叉树

平衡二叉树是1种特殊的二叉树,除了满足前面提到的搜索二叉树的特性,其还有1个约束条件:

  • 平衡二叉树左右子树的高度差的绝对值不超过1,并且其左右子树也分别为平衡二叉树。

    可以通过左旋和右旋的方式将非平衡的搜索二叉树转化为平衡二叉树。

  • 左旋

旧的根节点为新根节点的左子树,新根节点的左子树(如果存在)为旧根节点的右子树。

  • 右旋

旧根节点为新根节点的右子树,新根节点的右子树(如果存在)为旧根节点的左子树。

1棵平衡二叉树最多容纳多少节点呢?

假设树的高度为h,则平衡二叉树最多容纳 1+21+22+…+2(h-1)=2h-1。

如果整个平衡二叉树可以完整的加载到内存中,则其性能完全满足数据库索引的需求。但现实情况却是,当数据量比较大的时候,索引的大小可能有几个G甚至更多,当我们用索引查询的时候,很难将其全部加载到内存进行查找,能做的只有逐一加载每一个树节点,这里的树节点一般对应磁盘页或者磁盘页组合,我们姑且称之为数据块。

如果每个数据块存储10行数据,每个数据块代表树的1个节点,则1000万行数据需要高度为20的平衡二叉树,即从1000万行数据的平衡二叉树中找1行数据,最坏的情况下需要20次磁盘IO。在机械硬盘时代,从磁盘随机读取1个数据块需要 10ms 左右的寻址时间,也就是说,对于1个1000万行的表,如果采用平衡二叉树来存储,单独访问1行需要20个 10ms 的时间,这个查询可真够慢的。

为了让1个查询尽量少地读磁盘,就必须让查询过程中访问尽量少的数据块,而磁盘IO次数就是树的高度,所以我们需要想办法降低树的高度,使其变得矮胖,最容易想到的方案就是将二叉树调整为多叉树,让每一层能够容纳更多的节点。

而 B-Tree(Balance Tree) 就是典型的多叉平衡搜索树。

其实平衡二叉树的查询性能已经是最高的了,将二叉树改多叉树实属无奈之举(因为内存无法完全加载索引),这也就理解了为啥 HashMap 这类纯内存的集合,使用平二叉的红黑树,而不是多叉树。

B-Tree

需要注意的是: 中间的-符号不是减号,读作B树,而不是B减树。

一棵 m 阶的 B 树具有如下特征:

  • 根节点至少有2个子女;
  • 每个中间节点都包含 k-1 个元素和 k 个孩子,其中 m/2<=k<=m;
  • 每一个叶子节点都包含 k-1 个元素,其中 m/2<=k<=m;
  • 所有的叶子节点均位于同一层;
  • 每个节点中的元素从小到大排列,节点当中 k-1 个元素正好是 k 个孩子包含的元素的值域划分。

这条条框框的,听着就头大,别急,我们以3阶 B-Tree 为例:

重点看一下 (2,6) 节点,其包含2和6两个元素,又有3个孩子1、(3,5)、8。其中1小于元素2,(3,5) 在元素2和元素5之间,8大于元素6,正好符合刚才所列的几条特征。

基于 B-Tree 查找某元素时,元素之间的比较次数和平衡二叉树其实是一致的,但由于查找所经过的节点数量要小的多,也就意味着更少次数的磁盘IO,会极大的提高性能。

B-Tree 节点上除了索引元素,还包含卫星数据。

所谓卫星数据,指的是索引元素所指向的数据记录,比如数据库中的某一行。在 B-Tree 树中,无论中间节点还是叶子节点均带有卫星数据。

所以本质上数据库基于 B-Tree 实现索引时,每个节点是 key-data 的形式,key 就是数据的主键,data 就是具体的数据。

接下来我们说一下 B+ 树。

B+ Tree

B+ 树和 B 树有一些共同点,但也具备一些新的特性:

  • 每个中间节点都包含 k 个元素和 k 个孩子,其中 m/2<=k<=m;
  • 中间节点仅包含索引元素,不包含数据,所有数据均在叶子节点;
  • 所有叶子节点包含了全部元素的信息及指向这些元素的指针,且叶子节点本身根据关键字的大小有小到大顺序排列;
  • 所有中间节点的元素均存在于子节点,在子节点元素中是最大(或最小)元素。

直接举例说明:

可以看到,每一个父节点元素均出现在子节点中,是子节点中的最大(或最小)元素。如:根节点元素8是子节点(2,5,8)的最大元素,也是叶子节点(6,8)的最大元素。

需要注意的是,根节点的最大元素(这里是15),也就等同于整个 B+ 树的最大元素。后续无论插入删除多少元素,始终要保持最大元素在根节点之中。

至于叶子节点,由于父节点的元素均会出现在子节点中,层层向下传导,所有叶子节点包含了全部元素信息。并且每一个叶子节点都带有指向下一个节点的指针,形成1个有序链表。

同样的,由于叶子节点包含了全部元素信息,所以 B+ 树的卫星数据只存在于叶子节点中,中间节点仅包含索引元素。

这时候,有读者就会问了,如果 B+ 树的卫星数据只存在于叶子节点上,岂不是每次都得遍历到最底下一层,才能拿到相应数据,而 B-Tree 中间节点就包含有卫星数据,假设索引元素命中,在中间层就可以返回,岂不是平均IO次数更低,那为啥还要使用 B+ 树呢?

主要是因为在 B+ 树中,中间节点仅存储索引元素,而 B-Tree 的中间节点即存储索引元素又存储索引元素对应的卫星数据,所以同样存储空间的节点,显然 B+ 树可以容纳更多元素。比如建立100万条数据的索引,使用 B+ 树相比 B 树会使用更少的层数,即 B+ 树更矮胖,随之带来的就是磁盘IO次数的降低。

同时,由于 B+ 树的叶子节点是根据索引元素大小顺序排列的,所以对于范围查询,只要从上到下找到范围的下界叶子节点,然后顺着叶子节点的横向指针就可以将符合条件的所有元素遍历出来。如果使用 B 树,则需要进行繁琐的中序遍历。

综上,B+ 树相对于 B 树,具有如下优点:

  • 总元素个数相同的 B+ 树和 B 树相比,B+ 树更为矮胖(层高低),使得查询的 IO 次数更少;
  • B+ 树的所有叶子节点形成有序链表,便于范围查询。

其实,B+ 树还有1个隐含优势,就是 B+ 树的元素查询性能更为稳定。

因为 B+ 树的查询必须最终找到叶子节点,而 B 树只要找到匹配元素即可,无论匹配元素处于中间节点还是叶子节点。因此,B 树的查询性能并不稳定(最好情况是只查根节点,最差情况是查到叶子节点),而 B+ 树的每一次查询过程都是稳定的。

B+ 树的层数计算

在计算机中,磁盘存储数据的最小单元是扇区,1个扇区的大小是512字节,而文件系统(例如 XFS/EXT4)的最小单元是块,1个块的大小是 4K。

MySQL 默认的 InnoDB 存储引擎也有自己的最小存储单元–页(Page),1页的默认大小是 16K。

假设 B+ 树每个节点均代表1页。

先假设 B+ 树有2层,第1层为非叶子节点,保存索引字段和指针,第2层为叶子节点,保存索引字段和具体行记录。

那么,2层高度的 B+ 树能存储的行记录数=根节点的指针数*每个指针对应的第2层的行记录数。

  • 根节点的指针数

假设主键为 bigint 类型(8字节),InnoDB 的指针占用6个字节,则1个 page 能存放的指针数为 16K/(8+6) 约等于1170;

  • 叶子节点的行记录数

常规的互联网项目的单行记录大小约为1K,则1个 page 可以存储的行记录数为 16K/1K=16。

所以1个2层 B+ 树大概能存储的行记录数为 1170*16=18,720。

以此类推:

3层 B+ 树: 1170117016=21,902,400(约2190万);
4层 B+ 树: 117011701170*25,625,808,000(256亿)。

考虑到根节点的数据块总是在内存中,1个200亿行的表上1个 bigint 类型的索引,查找1行数据最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。

哈哈哈,看出来多叉树的威力了吧。

本文到此结束,感谢阅读!

参考文献

  • MySQL实战45讲(丁奇)
  • 漫画: 什么是B+树?(程序员小灰)

MySQL的InnoDB索引结构为啥选用B+树?相关推荐

  1. mysql innodb 索引结构_Mysql 学习笔记:InnoDB 索引结构浅析

    索引是检索图书资料的一种工具,把书刊中的内容或项目分类摘录,注明页数,按一定次序排列. 针对不同的数据存储结构有不同的数据查找方式. 1. 数据结构 1.1 B树 B树又名平衡多路查找树,主要用于文件 ...

  2. 【mysql innodb索引结构B+树】

    [mysql innodb索引结构B+树] 为什么Mysql中Innodb的索引结构采取B+树? B树 B树的两个明显特点 树内的每个节点都存储数据 叶子节点之间无指针相邻 B+树 B+树的两个明显特 ...

  3. MySQL怎么运行的系列(四)Innodb索引结构和方案

    本系列文章目录 展开/收起 MySQL怎么运行的系列(一)mysql体系结构和存储引擎 MySQL怎么运行的系列(二)Innodb缓冲池 buffer pool 和 改良版LRU算法 Mysql系列( ...

  4. mysql系列十、mysql索引结构的实现B+树/B-树原理

    一.MySQL索引原理 1.索引背景 生活中随处可见索引的例子,如火车站的车次表.图书的目录等.它们的原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的 ...

  5. 储存引擎InnoDB 索引选择 为何是B+树 而不是 B树 哈希表

    一:概述 首先需要澄清的一点是,MySQL 跟 B+ 树没有直接的关系,真正与 B+ 树有关系的是 MySQL 的默认存储引擎 InnoDB,MySQL 中存储引擎的主要作用是负责数据的存储和提取,除 ...

  6. MySQL之InnoDB索引的一些问题

    索引 索引常见的类型有哈希索引,有序数组索引,二叉树索引,跳表等等.本文主要探讨 MySQL 的默认存储引擎 InnoDB 的索引结构. InnoDB的索引结构 在InnoDB中是通过一种多路搜索树- ...

  7. MySQL 为什么用索引,为什么是 B+树,怎么用索引

    MySQL 索引 A database index is a data structure that improves the speed of operations in a table. Inde ...

  8. mysql为什么用索引_MySql为什么使用B+树做索引

    一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上.这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个 ...

  9. mysql两种索引结构_19.Mysql索引结构及常见索引的区别

    转载出:http://blog.csdn.net/qq_19557947/article/details/76951912 一.Mysql索引主要有两种结构:B+Tree索引和Hash索引 Hash索 ...

最新文章

  1. 优秀的 Java 项目代码都是如何分层的?
  2. KVM-Qemu-Libvirt三者之间的关系
  3. Android原生开发modules方式导入Unity问题汇总
  4. 如何获取Oracle数据库中某表及索引、约束、触发器、对象权限的创
  5. JS----JavaScript数组方法及总结
  6. event.target【转载】
  7. 2021牛客第五场 I.Interval Queries-回滚莫队
  8. WinForm 分屏 [ WinForm | Panel | 视频监控分屏 ]
  9. 程序员面试题精选100题:51-63解题报告
  10. 自定义竖着的SeekBar
  11. 4.4.4系统不用ROOT激活xposed框架流程
  12. LFS8.0完全安装搭建制作教程
  13. Scrapy学习笔记5——Spiders
  14. HARK学习(八)--LoadSourceLocation
  15. 毁掉一个孩子的几个方法 有多少家长正在这么做?
  16. android车载系统测试,一种车载Android多媒体主机的自动测试方法和系统与流程
  17. [iOS][转]iOS 架构模式 - 简述 MVC, MVP, MVVM 和 VIPER (译)
  18. PIL获取照片exif 批量修改手机照片名字为拍摄时间
  19. Java程序员必看经典书籍,助你早日打通任督二脉,Java菜鸟教程视频
  20. 企业如何建立完善的管理体系

热门文章

  1. java框架外文翻译_Spring框架-毕业论文外文文献翻译.doc
  2. python程序论文答辩_毕业论文答辩的程序是怎样的?
  3. Android 自定义RatingBar设置步长没起作用
  4. 修复移动硬盘“文件或目录损坏且无法读取”
  5. 在线客服软件海豚客服APP接入方法二:IOS篇
  6. 死亡——崔斯特·杜垩登
  7. 第一次微信小程序总结
  8. 详解深度优先搜索与回溯
  9. Java体系化学习路线图,带走不谢!
  10. 高通手机型号、开机logo、默认语言设置等小修改