MySQL索引底层的数据结构与算法

既然我们要探讨索引,那么我们就应该弄清楚索引是什么

1. 索引是什么?

MySQL官方给索引的定义为:索引是帮助MySQL高效获取数据的排好序的数据结构(索引是数据结构)

1.1 索引的本质

我们知道,数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法当然是顺序查找(linear search),这种复杂度为O(n)的算法在数据量很大时显然是糟糕的,好在计算机科学的发展提供了很多更优秀的查找算法,例如二分查找(binary search)、二叉树查找(binary tree search)等。如果稍微分析一下会发现,每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织),所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

这里重点要说的就是:索引是存储在文件里的

1.2 磁盘存取原理

上文说过,索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的。
如下图,是磁盘的整体结构示意图:
在这里插入图片描述

一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动(实际是斜切向运动),每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)。

磁盘结构的示意图,如图:
在这里插入图片描述

盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道,所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元。为了简单起见,我们下面假设磁盘只有一个盘片和一个磁头。

当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。

2. 索引底层数据结构与算法

2.1 索引结构

索引结构一般有:

  1. 二叉树
  2. 红黑树
  3. 哈希
  4. B-Tree

2.2 二叉树

在这里插入图片描述
图中展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。虽然这是一个货真价实的索引,但是实际的数据库系统几乎没有使用二叉查找树或其进化品种红黑树(red-black tree)实现的

2.3 B-Tree

  • 度(Degree)-节点的数据存储个数
  • 叶节点具有相同的深度
  • 叶节点的指针为空
  • 节点中的数据key从左到右递增排列

为了描述B-Tree,首先定义一条数据记录为一个二元组[key, data],key为记录的键值,对于不同数据记录,key是互不相同的;data为数据记录除key外的数据。那么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和指针互相间隔,节点两端是指针。

  7. 一个节点中的key从左到右非递减排列。

  8. 所有节点组成树结构。

  9. 每个指针要么为null,要么指向另外一个节点。

  10. 如果某个指针在节点node最左边且不为null,则其指向节点的所有key小于v(key1),
    其中v(key1)为node的第一个key的值。

  11. 如果某个指针在节点node最右边且不为null,则其指向节点的所有key大于v(keym),其中v(keym)为node的最后一个key的值。

  12. 如果某个指针在节点node的左右相邻key分别是keyi和keyi+1且不为null,则其指向节点的所有key小于v(keyi+1)且大于v(keyi)。

由于B-Tree的特性,在B-Tree中按key检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败

关于B-Tree有一系列有趣的性质,例如一个度为d的B-Tree,设其索引N个key,则其树高h的上限为logd((N+1)/2),检索一个key,其查找节点个数的渐进复杂度为O(logdN)。从这点可以看出,B-Tree是一个非常有效率的索引数据结构。

另外,由于插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质,本文不打算完整讨论B-Tree这些内容,因为已经有许多资料详细说明了B-Tree的数学性质及插入删除算法

2.4 B+Tree(B-Tree变种)

  • 非叶子节点不存储data,只存储key,可以增大度
  • 叶子节点不存储指针
  • 顺序访问指针,提高区间访问的性能

在这里插入图片描述
B-Tree有许多变种,其中最常见的是B+Tree,例如MySQL就普遍使用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更适合实现外存储索引结构,具体原因与外存储器原理及计算机存取原理有关,将在下面讨论。

2.5 带有顺序访问指针的B+Tree

一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针。

在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如图中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。

这一节对B-Tree和B+Tree进行了一个简单的介绍,下一节结合存储器存取原理介绍为什么目前B+Tree是数据库系统实现索引的首选数据结构。

B+Tree索引的性能分析:

  • 一般使用磁盘I/O次数评价索引结构的优劣
  • 预读:磁盘一般会顺序向后读取一定长度的数据(页的整数倍)放入内存
  • 局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用
  • B+Tree节点的大小设为等于一个页,每次新建节点直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,就实现了一个节点的载入只需一次I/O
  • B+Tree的度d一般会超过100,因此h非常小(一般为3到5之间)

3. MyISAM索引实现(非聚集)

MyISAM索引文件和数据文件是分离的

在这里插入图片描述

4. InnoDB索引实现(聚集)

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

在这里插入图片描述

联合索引的底层存储结构长什么样?

这里以MySql INNODB为例,MyISAM道理是一样的。然后先从原文搬几个图过来:

在这里插入图片描述

这是一张表格,col1 是主建,col2和col3 是普通字段。那么主索引 对应的 B+树 结构是这样子的:

在这里插入图片描述
现在呢,对col3 建立一个单列索引,原文图:
在这里插入图片描述
看完这个图也是可以理解的,那么想法来了,如果对 col3 和 col2 建立 联合索引,那么 B+ 树会是一个什么样子的呢?

首先可以肯定的是,肯定只有一棵树,又因为 最左原则的存在,那么带着这个想法自己试着画了下:
在这里插入图片描述
建索引语句 CREATE INDEX IDX_XXX ON TABLE(COL3, COL2);
先根据col3 排序,在根据 col2 排序,如上图。
原文例子中的数据没有重复数据,为了更好的理解,我自己改了下:
在这里插入图片描述

红色框是改动的地方,把col3 改成有重复数据了,然后 还是对 col3 ,col2建立联合索引,那么 B+树 如下:

在这里插入图片描述

红色框是和原来不一样的地方。
联合索引在查找的时候,比如要找 Alice,34 这条记录 WHERE COL3 = ‘Alice’ AND COL2 = 34
先根据col3 查找 Alice ,找到了2条记录,在根据col2 查找 34,然后获取到主键 15 ,在根据主键去查找 主索引。
如果 是 WHERE COL2 = 34,由于只有联合索引 (col3, col2),没有col2 的单列索引。
那么查找的时候,就没法根据上面的这棵树来查找 ,只能全表扫描。
所以为什么会有最左原则,就是因为 B+树 是根据最左边的字段构建的,我的想法是这样子的。

发布了100 篇原创文章 · 获赞 33 · 访问量 5822

猜你喜欢

转载自blog.csdn.net/JAVA_I_want/article/details/104027064