MySQL索引原理详解

学习MySQL数据库索引原理知识,同时了解与性能相关的优化实践。

讲述关于索引的原理,为后面数据库优化提供合适的方案。

索引的本质

mysql的索引做了合理的数据结构转换,查询庞大数据的情况下,极大的提高了效率。

本质

索引是能够在MySQL对数据进行排序,生成新的数据结构,最终实现高效率查询数据的一种方式。总的来说,索引就是一种数据结构。

索引底层结构

索引的数据结构有如下几种:

  • 二叉树
  • 红黑树
  • Hash表
  • B-Tree

数据库查询

下面是一张数据库的表,有两列数据,分别是Col1和Col2,存储的都是数字。

image.png

我们来查询一下数字为89的数据,mysql语句如下:

select * from s
where s.Col2 = 89

普通模式下,查询的规则是从上往下查询:

| 34 👇🏻

| 77 👇🏻
| 5 👇🏻

| 91 👇🏻

| 22 👇🏻

| 98 👇🏻

终止查询

如果数据表很大,查询的数据又是在表尾的话,那么需要花费非常多的计算时间。

优化?

那么有没有想过其他的优化方案呢,减少在有有序表中的计算?如果是单纯的数字比对,表中又有键值的话,我能想到的第一个就是算法中的二分查找。但是在这里是不适用的,因为数据是无序的。

二叉树

利用索引来实现优化,这里使用的二叉查找树

给Col2所有数据设置索引,给索引的每个节点是一个键值对(key/value),键保存的是所在行位置存储于磁盘的地址指针,value保存的是所在行位置的值。

例如:0x07:34

索引底层使用的数据结构是二叉树,最终存储的结构图如下:

image (1).png

如果我们再查询89的话,从根节点34开始查找,

| 34 👇🏻

| 89 👇🏻

终止查询

二叉树查询中,左边的子节点小于它的根节点,根节点小于右边的子节点。通过这个规律就可以很快的查找到对应的值。

效率得到了极大的提高,相比于原始的循序查询来说,减少很多不必要计算。但是索引底层用的并不是二叉树,因为二叉树对于有序列表来说是不能胜任的,我看下面的例子:

有序查询

下面是是二叉树存储有序的数据,我们都知道二叉树右边的子节点是小于根节点的,所以,有序的数据最终排列的结构和链表差不多。

image (2).png

我们查询6的结果是怎么样的呢?

select * from s
where s.Col1 = 6

结果如下:

12.gif

测试链接:https://www.cs.usfca.edu/~galles/visualization/BST.html

插入六个数字,可以看到查询的动画效果。

查询结果是,比对了六次才查找到最终结果,和原始查询并无差别,所以二叉树只在一定场景下有用。

AVL红黑树

二叉查找树存在不平衡的问题,那么可以通过树的叶子节点自动旋转和调整,让二叉树始终保持基本平衡的状态,这样就能够保持二叉查找树的最佳性能。基于以上思路的自调整平衡二叉树有AVL树和红黑树。

红黑树是一颗自动调整树形态的树结构,当二叉树处于一个不平衡状态下时,红黑树会自动左右旋转叶子节点以及节点变色(红色或者黑色),调整树的形态,使其保持基本的平衡,这个时候如果是查询节点,时间复杂度为O(logn),基本上保证了查找效率。使用二叉树插入七个节点,它的形态就如同一个链表,查询底部节点需要的时间复杂度达到了O(n),而使用红黑树则会自动化调节树的形态,使其达到平衡状态。可见下图:

image (3).png

如果上面的查询换成红黑树会是什么效果呢?

select * from s
where s.Col1 = 6

查询过程:

| 2 👇🏻

| 4 👇🏻

| 6 👇🏻

终止查询

动画效果:

1625450965712-bfc5cb14-ce37-481e-9592-b71bd8c8ea85.gif

通过上面的动画,可以看到,查询次数明显减少,效率上得到显著的提升。但是依旧还存在问题。

  • 数据非常大的情况下,叶子节点太深了
  • 每次查找叶子节点为一层,几十几百万层的查询,完全不可控

上面7个节点的形态有一点点的规律,就是有一些向右倾斜,尽管没有二叉树倾斜的厉害,但是倾斜的幅度也是挺大的,而这个只是7个节点而已,那如果有几十万上百万个节点,倾斜的幅度是不可容忍的,倾斜幅度带来的问题的查找的深度。对于查找性能来说是巨大的消耗。

所以应对这个问题,有人考虑到了AVL树,相对于了红黑树来说,AVL树是严格意义上的绝对平衡二叉树,因此在调整二叉树的形态上消耗的性能更多。

下图为VAL树:

image (4).png

相比于红黑树来说,节点的形态更加的平衡。按照层级来对比,红黑树有4层,AVL只有三层。

image (5).png

同样是查询节点6,结果会如何呢?

select * from s
where s.Col1 = 6

查询过程:

| 4 👇🏻

| 6 👇🏻

终止查询

查询动画:

13.gif

通过对比发现

  • 叶子节点层级减少
  • 解决向右倾斜的问题,形态上保持了平衡
  • 查询效率提升,大量顺序插入不会导致查询性能的降低,从根本上解决了红黑树遇到的问题

看起来AVL树作为数据查找的数据结构挺不错的,但是AVL树并没有被作为MySQL数据库的索引数据结构。因为数据库查找的瓶颈在于磁盘的I/O操作,如果使用AVL树,每一个树节点只存储一个数据,一次磁盘I/O只能读取一个节点的数据加载到内存中,假设依旧按照上面的查询6,则需要进行两次I/O操作,相比对内存计算来说,I/O操作是效率极低的,所以数据库索引首先考虑尽量减少频繁的I/O操作。

操作系统从上层到底层,各个层次之间存在着很多的I/O操作,比如CPU有I/O,内存有I/O,VMM有I/O,底层磁盘也有I/O。通常来说,一个上层的I/O可能会产生对磁盘的多个I/O,也就是说上层的I/O是稀疏的,底层的I/O是密集的。

这里我们只关注磁盘的 I/O,顾名思义就是磁盘的输入输出。输入指的是对磁盘写入数据,输出指的是从磁盘读出数据。磁盘读取10kb的数据和内存计算10kb的数据所消耗的时间的不一样的,磁盘读取所消耗的时间会比内存更长(感兴趣可以了解一下磁盘I/O性能指标,和CPU计算性能指标)。根据这个特性,可以在每一个节点中尽可能多的存储一些数据,一次磁盘I/O就多家在一些数据到内存中,这个就是B+树的设计原理。

B+树

B树是一种数据结构,它有如下特征

  • 叶节点具有相同的深度,叶节点的指针为空
  • 所有索引元素不重复
  • 节点中的数据索引从左到右递增排列
image (6).png

B树和B+树的区别:

  • 非叶子节点不存储data,只存储索引,可以放更多的索引
  • 叶子结点包含所有索引字段
  • 叶子节点用指针链接(链表链接),提高区间访问的性能
image (7).png

B+树中的根节点有15、56、77,在15和56中间的白色是下一个节点的磁盘文件地址。除了根节点是存储在内存中之外,其他的子节点都是存储在磁盘中。当查询的值不在15~56范围区间内,且不是15也不是56的时候,会读取磁盘地址,拉取磁盘文件到内存中(这个过程俗称I/O操作,I/O操作速度是挺慢的),在内存中进行比对查找。因为内存的计算和I/O操作完全不是一个量级的,内存使用的RAM计算。

数据存储量

想要计算MySQL数据存储量,首先就得知道根节点有多大,因为根节点的存储在内存中的,子节点才是存在磁盘中的,那么如何查看根节点的大小呢?

我们可以通过在Navicat Premium中进行sql语句的查询

show GLOBAL STATUS like 'Innodb_page_size'

结果如下:

image (8).png

最终查询到的大小是16kb。所以说MySQL设置一个节点最大是16kb。

一个节点的大小是8个字节,索引指针是6个字节(15~56中间白色的区域)

公式:16kb / (8b+6b) = 1170

所以,设置一个节点的话,最大可以存储1170个索引。

如果层级数为三层,即高度为3,那么可以存储多少数据呢?

公式:1170 * 1170 * 1170 = 16亿(****左右****)

哇,高度为三层,就可以存储怎么多是数据。那么如果查询的内容是20的话,只需要经过两次I/O操作,就可以查询到对应的值。

MyISAM索引

MyLSAM索引文件和数据文件是分离的,即为非聚集索引。下面讲解一下文件结构和数据结构

MyISAM的数据库一般是存在三个文件中的,分别是后缀为frm、MYD、MYI的文件,如下图:

image (9).png

每个文件的作用分别是:

  • frm:存储表结构相关的信息
  • MYD:表数据
  • MYI:表索引,就是我们建表时候是主键

下面是索引的结构图:

image (10).png

索引查询流程

如果有一个查询:

select * from s
where s.Col1 = 30

如果使用的是索引查询,则会读第一个根节点,查找到30,它处于在15~56中间,则会进行一次I/O操作,进入到MYI文件中进行找到对应的索引,将读取的文件交给内存执行B+树查找,为没有找到数据则会进行第二次I/O操作,最终读取到了30这个索引值,索引值存储的是一个索引所在行的磁盘文件地址,这个地址是指向的MYD后缀文件,所以会进行I/O读取MYD后缀的数据表文件读取对应行的值。

以上就是MyISAM索引引擎底层搜索所经历的流程。

InnoDB索引

InnoDB索引文件是合在一起的,即索引和数据存在一个文件,因此它属于聚集索引

InnoDB的数据库一般存在两个文件中,分别是后缀为frm、ibd的文件,如下图:

image (11).png

每个文件的作用分别是:

  • frm:存储表结构相关信息
  • ibd:存储表的索引,和所有的数据

下面是索引的结构图:

image (12).png

特性

  • InnoDB的叶子节点存储的是每个索引所在行的所有数据字段,即所在行完整的数据。(参见最底层叶子节点索引15,下面是所在行的数据34、Bob)
  • 表文件本身就是B+树组织的一个索引结构文件
  • 由于主键和数据都是在一个文件内的,所有InnoDB必须要有主键,并且推荐使用整型的自增主键
  • 非主键索引结构的叶子节点存储的是主键值,这个是为了实现一致性,节省存储的空间

小结

  • 索引底层使用的是B+Tree结构。
  • 索引最底层的叶子节点是排好序的链表,可以访问左右相邻节点。
  • 数据库类型有很多种,比如MyISAM、InnoDB,它们区别于聚集索引和非聚集索引。
  • 聚集索引是索引和数据都存储在一个文件中,非聚集索引是将索引和数据分别存储在不同的文件,索引键指向的是数据文件索引所在行的数据内容。

下面来解答一下几个问题。

聚集索引和非聚集索引哪个效率更高

以MyISAM数据结构和InnoDB数据结构来说:

非聚集索引查询到索引值之后,只是拿到了索引所在行的磁盘文件地址,需要通过这个地址再进行一次I/O操作。

聚集索引读取到叶子节点索引值之后,即那到了索引所在行的完整的数据内容,不需要额外的I/O操作。

因此,聚集索引效率更高!

为什么InnoDB必须要设置索引

因为InnoDB是聚集索引,索引和数据都是存到在一个文件中的,如果没有索引的话,全部数据就没办法合理的组织起来。

如果放肆,就是不加索引怎么办?

如果使用InnoDB建表的时候,没有显式的指定一个主键,则会自动帮你找到一个合适的唯一索引作为主键,若找不到符合条件唯一索引条件的字段时,会生成类似于ROW_ID的虚拟列充当该表的主键。

为什么主键建议设置为整型自增

在索引查找的过程中,其实做的是比对计算,通过大小比对,知道所查询的索引在什么区间范围内,从根节点进行比对,根节点没有,再到叶子节点进行比对,这个过程中,使用整型是效率比较高的一种方式。

如果使用的是字符串对比,例如要查找一个索引是asdf20,根节点的区间范围是asdf15~asdf56,那么查询过程中需要对asdf20进行字符串拆分,逐个逐个字符串进行比对,字符串比对过程中还需要转换成ASCII码,然后再进行大小比对,这个效率是及其底下的。

为什么要自增?

在创建表之后,数据库引擎会自动将主键进行排序。我们手动的设置为自增能够,减少引擎的额外计算,要不每次添加一个数据,数据库引擎都会进行所有数据排列,当短时间内处理的数据量很大时,会损耗特别多计算量。

有序链表的好处

之所以引擎会将底层叶子节点设置为有序的,是为了很好的处理一些业务场景,比如范围查找,如下面查找语句:

select * from s
where s.Col2 > 20

查找的节点不是单一节点,而是范围节点,查找的流程就是先在根节点找到索引为20的这个节点,找到之后,向下找出其底层叶子节点,利用有序链表的特性,直接把大于20的所有数据存储到集合中,输出即可!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容