引言

最近看了一个公开课,是有关MySQL对索引设计的思考。详细讲解了几种索引实现的设计思考与利弊辨析,讨论了为什么MySQL默认情况下会使用B树索引,B+树索引又对B树做了哪些结构改进。

本片博客通过个人的学习理解和总结,由几种简单的索引数据结构,逐渐展开对B+树索引的探究和思考。

一、常见的索引实现

索引是帮助MySQL 高效获取数据的有序数据结构。它可以在几十毫秒,最多几百毫秒内快速定位数据在磁盘中的位置。它并非是MySQL中特有的概念,实际上很多地方都有索引思想,如 Java 中的 HashMap 其本身就可以理解为一种索引。

索引有以下几种常见的可供实现的数据结构:

  1. 二叉树
  2. 红黑树
  3. Hash表
  4. B-Tree

1.1 二叉树实现下的索引

二叉树可能是最容易被想到用作索引实现的一种方式,实际其他很多索引的实现都是二叉树的变种。下图是一个普通的二叉树实现索引的基本方式:

如上图,左侧是表中记录,右侧是建立在 col2 上的索引,由根节点向下,每个节点的左子节点要小于自身,右节点要大于自身,以此来形成一种有序的结构。每个节点不仅存储 col2 列上的值,同时还会指向其对应记录的磁盘地址。如col2 = 89,其磁盘地址为 0x77。

由于表数据存储于磁盘上,且表数据不可能全部加载到内存中供程序查询,在没有建立索引的情况下,如果想要找到 col2 = 89 这条记录,就必须从表中的第一行记录开始,执行6次磁盘IO 操作,而建立了 col2 列的索引后,col2 = 89 只需要从索引的根节点开始执行1次磁盘IO即可找到对应记录,避免了全表扫描,从而极大地降低了磁盘IO,提高了性能。

1.2 普通二叉树索引的弊端

还是以下图为例,如果我们的索引建立在 col1 上,思考索引查找会有怎样的问题?

根据表中的数据,col1 本身是呈现一种递增的态势,建立索引的过程也是从表中第一条数据开始,以此将列值放入二叉树中,那么上图中的数据就会组织成如下图所示的二叉树索引结构:

只有单右侧的二叉树,已经退化为一个链表,而链表对于查找操作来说是个非常糟糕的结构,完全无法提升搜索性能。

这是因为普通的二叉树并没有元素平衡分配规则,对树的高度做有效的控制,才会出现这种情况。所以 MySQL并没有采用这种简单的二叉树结构。

1.3 红黑树索引

红黑树对于了解 Java 8 HashMap 底层实现的开发者并不陌生。Java 中的 HashMap实现由 Java 7 “数组 + 链表”的组合变为了如今的“数组 + 红黑树”,链表变为红黑树,就是对查找性能的重大革新。

红黑树也是二叉树的一种,确切的说应该是平衡二叉树的一个子类。所谓平衡二叉树,就是利用一定的平衡规则,将元素尽可能地分配到根节点的两侧,尽量弥补普通二叉树在单边增长情况下退化为链表的缺陷。

我们来看下实现了 Java 8 HashMap的红黑树会如何为 col1 建立索引。

首先,为红黑树添加col1 = 1:

然后,为红黑索引树添加 col1 = 2,从根节点开始,由于2 >= 1,继续查找根节点右子树,又由于右侧无任何元素,直接插入:

继续添加 col1 = 3,从根节点开始,3 >= 1,查找根节点右子树,3 >= 2,继续查找右子树,右侧无元素,插入元素,由于节点和父节点都是红色,且节点本身是右子节点,父节点也是右子节点,根据红黑树的调整规则,旋转以调整额外的红色节点:

依据红黑树的插入规则,最后生成的索引结构为:

可以看到,经过红黑树的优化,一定程度上解决了二叉树退化链表的问题。

虽然利用红黑树这种平衡的特性,但MySQL 依然没有使用红黑树作为索引的实现。

这是因为,红黑树依然无法有效控制树的高度。

当表中的数据多达上百万、千万的级别,对某个列建立索引,意味着需要有上百万个索引值。这样庞大的树结构高度可能会达到20 ~ 30,即便是在内存中对树节点进行检索,依然可能需要搜索20 到 30 次才能够找到对应的索引值。这就是MySQL 索引实现的又一个不得不重视的因素——树的高度。

很遗憾,红黑树对树的高度不可控,无法满足 MySQL 对索引的要求。

1.4 B树与B+树

为了降低树的高度,树中节点必须横向扩展以容纳足够的索引值。

B-Tree(B树,也叫多叉平衡树)具有以下一些重要特点:

  1. 所有元素不重复;
  2. 叶子节点具有相同的深度,叶子节点指针为空;
  3. 节点中的数据从左到右递增排列。

结构简图如下所示:

B树索引在使用的时候,会先把根节点整个加载到内存中,然后在根节点中随机查找。

比如,我们需要查找 49 这个节点,那么先将根节点整个加载到内存,发现49在15和56之间,那么会将对应的节点整个加载到内存,再重复上述步骤,直到找到49。

以col1为例,构建B树索引(假设树最大高度为3)如下:

最大高度为4时,col1的B树索引如下:

B树会根据设定的最大深度(即树高度)来调整每个节点所能容纳的元素个数,因此,根据最大深度的不同,可能特定的B树也会有不同的结构。

那每个节点最大容量是多少呢?

内存与磁盘的IO交互有一个数据量限制,一般是4KBytes。因此,MySQL为了将节点加载到内存的效率最大化,设置一个文件的默认“分页大小”,它恰好是读取量限制的4倍,即16K:

SHOW GLOBAL STATUS LIKE 'Innodb_page_size';Variable_name     Value
----------------  -----
Innodb_page_size  16384   

实际上,MySQL并没有安全遵照B树来设计索引,而是采用了B树的变种——B+树。

B+树的重要特性有如下这些:

  1. 非叶子节点不存储data,只存储索引(冗余),可以放更多的索引;
  2. 叶子节点包含所有索引值;
  3. 叶子节点用指针连接,提高区间访问的性能。

我们需要关注MySQL的B+树索引对 B树的两步优化。

第一点是将所有元素的数据部分移动到了叶子节点上,非叶子节点不存储任何地址数据。

这是因为考虑到每个节点不能太大,MySQL对其限制为16K,因此,在大小限制的情况下,将数据的存储空间让出,那么每个节点就可以容纳更多的“索引”,从而极大的提高了索引树每个节点的数据容量。

以主键为 bigint 类型为例,我们计算一下B+树的索引容量。

非叶子节点每个元素后面会有一个指向下一个节点的地址,MySQL底层设置该值大小为6Bytes,而bigint类型的大小是 8Bytes,因此非叶子节点中的每个索引元素占8 + 6 = 14 Bytes,根据前面的说明,每个节点大小为16KBytes,那么一个非叶子节点最多可以容纳16 * 1024/14 = 1170个索引元素。

对于一个最大深度为3的B+树,在深度为3 的叶子节点上,每个元素的数据大约1K(实际上大部分建立索引的元素远不会有这么大),根据每个节点最大16K的限制条件,那么每个叶子节点最多可以容纳16个索引元素,因此,这个B+树的最大容量可以为1170*1170*16 ≈2000+万,而实际上B+树的存储容量要比这个数字大很多。

B+树的叶子节点包含全部索引元素,而其他非叶子节点中的索引元素是一种“冗余”元素,它们也是索引元素,但不具备存储具体磁盘地址的功能,只作为构建B+树的查找路径的功能型存在。

第二点是所有叶子节点之间会使用双向指针连接,而B树本身所有叶子节点中的索引元素从左到右依次递增,那么根据这样的结构,如果有一个条件WHERE col >20,那么MySQL只需要定位一次索引元素的位置,就可以快速获取该索引元素后面的全部索引元素,极大的降低了索引搜索的次数。

1.5 Hash索引

Hash算法会对目标参数进行散列,从而得到一串“随机的”编码。

MySQL同样支持Hash索引,与树型索引不同,Hash索引可以快速定位到具体的索引值,其性能是树型索引远远赶不上的。

当我们通过 WHERE 子句查询某个列值的时候,比如:WHERE col1 = 89,MySQL会首先分析该列是否有建立的索引,如果col1建立了一个Hash索引,那么MySQL会对89进行同样的散列操作,获得该记录存储的磁盘地址,然后直接取得数据。

但是为什么绝大多数情况下我们不会用到Hash索引呢?

这是因为通过Hash算法建立的索引对于范围查询,完全排不上用场。

所以,根据这样的特性,如果确定某个列只会用到精确查找,那么可以考虑使用Hash索引来进一步提升性能,当然,这个秘密武器可能只有在极少的情况下才能派上用场了。

二、MySQL存储引擎的B+树索引

B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据。

2.1 InnoDB 索引实现

InnoDB 是以聚集的方式实现索引的。

  1. 表数据文件本身就是按 B+Tree 组织的一个索引文件
  2. 聚集索引-叶子节点包含了完整的数据记录
  3. 为什么InnoDB 必须有主键,并且推荐整型的自增主键?
  4. 为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省存储空间)

InnoDB会将表数据以及索引都存储在 .ibd 文件内,其内部结构如下图示例:

InnoDB索引是一个典型的聚集索引,所谓聚集索引就是叶子节点中的索引不仅存储了索引元素,同时也包含了索引值所在行的其他列值。而MyISAM的索引与数据是存储在两个文件中(数据存在.MYD,索引存在.MYI),因此索引元素只存储了对应的数据地址,是非聚集型索引。由于聚集所有会存储数据信息,所以查找效率要比非聚集索引高。

InnoDB必须为表添加一个主键列,这样才能通过主键列将表数据组织成一个B+树,这也是由于InnoDB聚集索引的存储形式所决定的。如果开发者没有给InnoDB表添加主键,MySQL会自动从表的第一列开始寻找适合作为主键的列,如果无法找到,那么就在后台自动为表添加一个类似主键的rowid列,以维护B+树结构。

InnoDB表建议使用整型作为主键,因为这种整型数据维护的B+树,可以快速进行索引的比较并定位,如果使用类似UUID的字符串作为主键,那么不论是维护主键树还是查找主键,都会一定程度上限制索引元素比较的速度,从而影响整体的性能。

那为什么又建议使用自增主键呢?这是因为B+树的所有叶子节点中的所有元素要求必须从左到右依次递增,为了维护这样的结构,如果在主键中有非递增的情况,就必须向中间插入索引元素保持递增,这就可能会涉及到结构的旋转、冗余节点的提升、树结构的平衡等多项操作,严重影响B+索引树创建的效率。

2.2 MyISAM索引实现

MyISAM索引文件和数据文件是分离的(非聚集)

MySQL 高级 —— 索引实现的思考相关推荐

  1. MySQL高级-索引

    索引 1.索引概述 2.索引优势劣势 3.索引结构 3.1 BTREE 结构 3.2 B+ TREE 结构 3.3 MySQL中的B+ 树 4 索引分类 5 索引语法 5.1 创建索引 5.2 查看索 ...

  2. MySQL高级-索引优化(超详细)

    性能分析 MySQL Query Optimizer Mysql中由专门负责优化SELECT语句的优化器,主要功能就是通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计 ...

  3. MySQL 高级 - 索引 - 索引设计原则

    索引设计原则 ​ 索引的设计可以遵循一些已有的原则,创建索引的时候请尽量考虑符合这些原则,便于提升索引的使用效率,更高效的使用索引. 对查询频次较高,且数据量比较大的表建立索引. 索引字段的选择,最佳 ...

  4. MySQL 高级 - 索引 - 概述

    索引概述 MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构(有序).在数据之外,数据库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数 ...

  5. Mysql高级——索引篇

    索引 1. 索引的概念 索引(index)是帮助 MySQL 高效获取数据的数据结构(有序).在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就 ...

  6. Mysql高级 索引优化

    Mysql逻辑架构 Mysql与其他数据库相比有点与众不同,他的架构可以在多种不同的场景中应用并发挥作用,主要体现在存储引擎的架构上,插件式的存储引擎结构将查询处理和其他的系统任务以及数据的存储提取分 ...

  7. MySQL高级-索引是个什么东西?explain到底怎么用-MySQL查询优化大全

    目录 一.引出问题-MySQL的查询优化: 二.性能下降的原因: 三.索引到底是什么?怎么用? 1.索引操作 查看索引: 删除索引: 创建索引: 说明: 索引命名规范: 2.索引优势: 3.索引劣势: ...

  8. MySQL高级-索引是什么

    目录 什么是索引 索引优势: 索引劣势: 索引分类: mysql索引结构: 哪些情况需要创建索引: 哪些情况不要创建索引: 索引操作: 什么是索引 MySQL官方对索引的定义为:索引(index)是帮 ...

  9. MySQL 高级 - 索引 - 数据结构

    索引结构 索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的.所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型的.MySQL目前提供了以下4种索引: BTR ...

最新文章

  1. Linux cp命令如何拷贝整个目录下所有文件
  2. Linux06-服务、守护进程和systemd
  3. C语言--测试电脑存储模式(大端存储OR小端存储)
  4. MySql 创建索引原则
  5. 2019-1-17王志颖 c语言作业
  6. 回溯算法团灭子集、排列、组合问题
  7. 【传递闭包】【倍增】幸福路径(P4308)
  8. (19)Xilinx PCIE中断理论(学无止境)
  9. Android Hello World 实例【TODO】
  10. react 如何引入打印控件 CLodop
  11. 图像增强论文Range Scaling Global U-Net for Perceptual Image Enhancement on Mobile Devices阅读笔记
  12. java 事务级别_java事务隔离级别
  13. 使用application对象实现网站访问量统计
  14. 宝塔面板关键目录解析
  15. Python实现数列求和
  16. python实现蜂鸣器演奏两只老虎
  17. Pr菜鸟入门教程(剪辑部分)
  18. ORACLE —— 事务
  19. jmetter持续时间_【转】Jmeter做web压力测试时设置持续时间注意点
  20. 国际菜鸟网络露头 阿里2.49亿美元投资新加坡邮政

热门文章

  1. 单位矩阵属性(I ^ k = I)| 使用Python的线性代数
  2. 面试官:final、finally、finalize 有什么区别?
  3. 线程池是如何重复利用空闲的线程来执行任务的?
  4. halcon 17 cuda cudnn 深度学习环境搭建
  5. EF中DB First模式下数据库中表结构变化时如何快速同步到EF模型中
  6. uniapp+typeScript+vue3.0+vite
  7. mysql设置输出格式_rsyslog 配置mysql输出格式
  8. 台式电脑如何设置开机密码_设置苹果Mac电脑的开机密码-macw资讯
  9. 计算机知识竞赛决赛流程,计算机知识竞赛决赛圆满结束!还不快戳?!
  10. Win11系统如何设置任务栏新消息提醒