前言

MySQL的索引是一个非常重要的知识点,也基本上是面试必考的一个技术点,所以非常重要。那你了解MySQL索引的数据结构是怎么样的吗?为什么要采用这样的数据结构?

现在化身为MySQL的架构师,一步步迭代设计出MySQL的索引结构,保证你再也忘记不了索引的结构了,轻松通过面试。

索引介绍

MySQL表中存储的数据量非常大,可能有上亿条记录,如果一条条去匹配,就是所谓的全表扫描,会非常的慢。那么有什么办法呢?

想想我们生活中的例子,比如新华字典,我们有一个目录,目录根据拼音排序,内容包含了汉字位于字典中具体的的页码。聪明的你肯定也想到了,我们也可以借鉴这种思想,建立一个MySQL的目录,叫做“索引”。

所以你对“索引”做了抽象和定义:索引(Index)是帮助MySQL高效获取数据的数据结构。

索引是在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,MySQL有InnoDB、MyISAM、Memory等存储引擎,你想了下,就拿最常用的InnoDB作为存储引擎设计索引。

索引设计目标

你现在拼命转动大脑,开始去思考如何设计出这样的一个索引结构。你就在脑子里想,索引设计中需要解决哪些问题,以及要达成什么样的目标。

  1. 我要怎么样才能在索引目录(数据结构)中快速找到具体的某条数据记录呢?那么这个数据结构需要有顺序规律,我按照这个规律就可以定位到具体的某条数据。
  2. MySQL中的数据中的记录如何能够快速找到呢?是不是可以将记录进行排序,然后根据 二分法 快速找到对应的数据记录。
  3. MySQL中架构老大一开始定义数据是按照数据页存放的,每个数据页默认是16kb, 每次满了,就会重新有新的一页。我的索引目录数据应该也是放到页中,而且索引的数据尽量少些,这样每页可以放更多的目录信息。
  4. 我怎么样才能查询效率最高呢?其实每次慢都是慢在磁盘IO上,我再后面设计中一定要减少磁盘IO的访问,越少访问磁盘IO越好。
  5. 磁盘中的空间还是不连续的啊,那我还得有个指针去连接下一条记录的位置。

带着这些问题和思考,你开始设计啦。

索引设计迭代

你想着我就拿一个例子具象化的思考设计索引。

下面是一个新建的表:

CREATE TABLE demo(
c1 INT,
c2 INT,
c3 CHAR(1),
PRIMARY KEY(c1)
) ROW_FORMAT = Compact;

行记录的格式简化如下:

我们只在示意图里展示记录的这几个部分:

  • record_type:记录头信息的一项属性,表示记录的类型, 0 表示普通记录、 2 表示最小记录、 3 表示最大记录、 1 暂时还没用过,下面讲。
  • next_record:记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。
  • 各个列的值:这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。
  • 其他信息:除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。

把一些记录放到页里的示意图就是:

注意:一页可以存放16kb的数据,并不是图上的3条数据,这里只是一个示例。

迭代一

我们为什么要遍历所有的数据页或者记录?因为各个页中的记录并没有规律,不知道这条数据出现在哪个数据页中。那么如何快速定位要查找的数据在哪个数据页中呢?我们需要建立一定的规律,如下:

  1. 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。

  • 页中的数据根据主键按顺序排序
  • 不同页中的数据,下一页数据大于上一页数据
  • 新分配的数据页编号可能并不是连续的。它们只是通过维护者上一个页和下一个页的编号而建立了 链表 关系
  1. 给所有的页建立一个目录项

  • key表示目录中最小的主键值。
  • page_on表示对应的页码。

查找主键值为 20 的记录,具体查找过程分两步:

  1. 先从目录项中根据二分法快速确定出主键值为 20 的记录在 目录项3 中(因为 12 < 20 < 209 ),它对应的页是页9。

  2. 再根据前边说的在页中查找记录的方式去页9中定位具体的记录。

迭代二

迭代一中的目录项是怎么存储的呢?我们是不是也可以用行记录格式存储到数据页中呢。答案是肯定的,我们通过行记录格式中的record_type等于1表示是目录记录,如下图所示:

  • 目录项记录的 record_type 值是1,而 普通用户记录的 record_type 值是0。
  • 目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列 ,另外还有InnoDB自己添加的隐藏列。

现在以查找主键为 20 的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:

  1. 先到存储目录项记录的页,也就是页30中通过二分法快速定位到对应目录项,因为 12 < 20 < 209 ,所以定位到对应的记录所在的页就是页9。

  2. 再到存储用户记录的页9中根据二分法快速定位到主键值为20的用户记录。

迭代三

随着数据量变多,势必一个目录项存放不下,因为一页只有16kb大小,就会分裂出多页,如下图所示:

那么现在查找主键值为 20 的记录,流程如下:

  1. 我们现在的存储目录项记录的页有两个,即页30和页32 ,又因为页30表示的目录项的主键值的 范围是 [1, 320) ,页32表示的目录项的主键值不小于 320 ,所以主键值为 20 的记录对应的目录项记录在页30中。

  2. 通过目录项记录页确定用户记录真实所在的页。

  3. 在真实存储用户记录的页中定位到具体的记录。

迭代四

如果我们表中的数据非常多则会产生很多存储目录项记录的页,如果直接这么查,也是很慢,我们是不是可以针对目录项记录的页再生成一个更高级的目录,就像是一个多级目录一样,如下图所示:

那么现在查找主键值为 20 的记录,流程如下:

  1. 生成了一个存储更高级目录项的页33,这个页中的两条记录分别代表页30和页32, 主键20的记录在 [1, 320) 之间,则到页30中查找更详细的目录项记录。

  2. 在页30中通过二分法查找主键为20记录的用户记录页码。

  3. 在真实存储用户记录的页中定位到具体的记录。

迭代小结

以上这个数据结构就是我们索引最终的数据结构,B+树, 图形描述如下:

  • 所有的叶子节点存放全量的用户记录信息,包含所有的字段。
  • 所有的目录节点只存放索引字段、主键以及对应的页码信息,要求信息越少越好,因为一页最多16kb,只有目录信息越少,每页存放的信息越多,树的层级就越小,树的层级越小,那么和磁盘的IO就越少,查询就会越快。一般来说,B+树4层,就可以存放上亿数据了。

索引结构总结

聚簇索引

我们按照前面的迭代推演出了基于主键的索引结构,是一颗B+树,我们把这种索引叫做聚簇索引。

特点:

  • 聚簇索引中的叶子节点存放了用户记录的全部数据,它就是innoDB中数据存放的格式,即数据即聚簇索引,聚簇索引即数据,这也是聚簇索引名字的由来吧,数据和索引聚集在一起。
  • InnoDB要求表必须有主键。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐 含字段作为主键,这个字段长度为6个字节,类型为长整型,这样始终就会有一个聚簇索引。

非聚簇索引

既然有了聚簇索引,那么肯定有非聚簇索引,非聚簇索引也叫二级索引或者辅助索引。

它是在什么场景出现的呢?比如我们想以别的列作为搜索条件,总不能是从头到尾沿着链表依次遍历记录一遍,肯定要慢死了。这时候就需要建立非聚簇索引,那它的索引结构和聚簇索引有什么区别呢?

  • 索引目录的内容由3部分组成,索引列的值+主键值+页码,通过索引列的值+主键值唯一确定新插入的列是在哪个页中,也可以唯一确定从那个页中查询。
  • 索引的叶子节点存放内容为索引列的值+主键值。

那可能你有疑问了,只有主键值,我要查记录的其他信息怎么办呢?

我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根 据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就 是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树!

回表的过程会耗时,为什么不直接存放所有的数据记录呢?

如果把完整的用户记录放到叶子结点是可以不用回表。但是太占地方了,相当于每建立一课B+树都需要把所有的用户记录再都拷贝一遍,这就有点太浪费存储空间了。

联合索引

联合索引是一种特殊的非聚簇索引,那么它的数据结构又是怎么样的呢?

比方说我们想让B+树按照c2和c3列的大小进行排序,为c2和c3建立的索引的示意图如下:

  • 每条目录项都有c2、c3、主键、页号这4个部分组成,各条记录先按照c2列的值进行排序,如果记录的c2列相同,则按照c3列的值进行排序
  • B+树叶子节点处的用户记录由c2、c3和主键c1列组成。

索引优点和缺点

我们在了解了索引的数据结构以后,就更加明白索引的优缺点了。

优点

  1. 提高数据查询的效率,降低数据库的IO成本。
  2. 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。
  3. 在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,降低了CPU的消耗。

缺点

  1. 创建索引和维护索引要耗费时间,并且随着数据量的增加,所耗费的时间也会增加。
  2. 索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间
  3. 降低更新表的速度。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。
  4. 索引中的数据都是有序的,比如插入一条主键较小的数据,势必导致其他数据进行移动,页码发生调整,这种现象也叫做页分裂,这也是为什么推荐主键要求自增。

总结

本为让你亲身作为一个MySQL架构师的身份,一步步带你理解MySQL中索引的数据结构,现在是不是理解的很透彻了,如果对你有帮助的话,请留下一个赞吧。

更多学习资料请移步:程序员成神之路

一步步带你设计MySQL索引数据结构的更多相关文章

  1. Mysql索引数据结构为什么是B+树?

    目录 Mysql索引数据结构 二叉树 红黑树 B-Tree B+Tree Mysql索引数据结构 下面列举了常见的数据结构 二叉树 红黑树 Hash表 B-Tree(B树) Select * from ...

  2. mysql 索引数据结构及原理

    原文:http://www.uml.org.cn/sjjm/201107145.asp 1 索引的本质 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构.提取句子 ...

  3. MySQL索引----数据结构及算法原理

    摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ...

  4. mysql索引数据结构

    什么是索引?索引就是排好序的数据结构,可以帮助我们快速的查找到数据 推荐一个网站,可以演示各种数据结构:https://www.cs.usfca.edu/~galles/visualization/A ...

  5. Mysql索引数据结构详解(1)

    慢查询解决:使用索引  索引是帮助Mysql高效获取数据的排好序的数据结构 常见的存储数据结构: 二叉树    二叉树不适合单边增长的数据 红黑树(又称二叉平衡树)    红黑树会自动平衡父节点两边的 ...

  6. B+/-Tree原理(mysql索引数据结构)

    B+/-Tree原理 B-Tree介绍 B-Tree是一种多路搜索树(并不是二叉的):       1.定义任意非叶子结点最多只有M个儿子:且M>2:       2.根结点的儿子数为[2, M ...

  7. 设计Mysql索引的原则

    1. 搜索的索引列,不一定是所要选择的列.换句话说,最适合索引的列是出如今WHERE 子句中的列,或连接子句中指定的列,而不是出如今SELECT keyword后的选择列表中的列. 2. 使用惟一索引 ...

  8. 图灵学院Java架构师-VIP-【性能调优-Mysql索引数据结构详解与索引优化】

    最近报名了图灵学院的架构专题的付费课程,没有赶上6月份开课,中途加入的.错过了多线程的直播课程,只能看录播了

  9. 深入理解MySQL索引底层数据结构

    作者:IT王小二 博客:https://itwxe.com MySQL 索引相关的数据结构有两种,一种是 B+tree,一种是 Hash,那么为什么在 99.99% 的情况下都使用的是 B+tree索 ...

  10. 「 MySQL高级篇 」MySQL索引原理,设计原则

    大家好,我是melo,一名大二后台练习生,大年初三,我又来充当反内卷第一人了!!! 专栏引言 MySQL,一个熟悉又陌生的名词,早在学习Javaweb的时候,我们就用到了MySQL数据库,在那个阶段, ...

随机推荐

  1. 第一个Django应用 - 第三部分:Django视图和模板

    一.概述 一个视图就是一个页面,通常提供特定的功能,使用特定的模板.例如:在一个博客应用中,你可能会看到下列视图: 博客主页:显示最新发布的一些内容 每篇博客的详细页面:博客的永久链接 基于年的博客页 ...

  2. Beats:运用 Filebeat 来对微服务 API 进行分析

    文章转载自:https://elasticstack.blog.csdn.net/article/details/118145104 需要学习的是httpjson请求的写法 使用 Filebeat 的 ...

  3. 使用gitlab+jenkins+nexus拉取springcloud并根据不同模块构建docker镜像,并推送到nexus里的docker仓库

    1.安装gitlab 详情看:https://www.cnblogs.com/sanduzxcvbnm/p/13023373.html 安装好gitlab后,然后创建一个普通用户,编辑用户,给用户设置 ...

  4. PostgreSQL 选择数据库

    数据库的命令窗口 PostgreSQL 命令窗口中,我们可以命令提示符后面输入 SQL 语句: postgres=# 使用 \l 用于查看已经存在的数据库: postgres=# \l List of ...

  5. Flutter Cocoon 已达到 SLSA 2 级标准的要求

    文/ Jesse Seales, Dart 和 Flutter 安全工作组工程师 今年年初,我们发布了 Flutter 2022 产品路线图,其中「基础设施建设」这部分提到:2022 年 Flutte ...

  6. 9.channels layers

    settings.py配置 # 存储在内存里 CHANNEL_LAYERS = { "default": { "BACKEND": "channels ...

  7. 3.pygame快速入门-游戏循环及动画实现

    游戏循环的开始,意味着游戏的正式开始,游戏循环的作用如下 1.保证游戏不会直接退出 2.变化图像的位置--动画效果 3.检测用户交互--按键.鼠标等     游戏时钟 pyagame提供了一个pyga ...

  8. Codeforces 1672 E. notepad.exe

    题意 这是一道交互题,有n个字符串,每个字符串长度:0-2000, n :0-2000 有一个机器对他进行排版,你可以给他一个每行的最大宽度w,那么每行只能放长度为w的字符: 每行相邻两个字符串之间至 ...

  9. 八、Django的组件

    8.1.中间件 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出.因为改变的是全局,所以需要谨慎实用,用不好会影响 ...

  10. mybatis-自定义映射resultMap

    自定义映射resultMap resultMap处理字段和属性的映射关系 resultMap:设置自定义映射 属性: id:表示自定义映射的唯一标识,不能重复 type:查询的数据要映射的实体类的类型 ...