跳槽必看MySQL索引:B+树原理揭秘与索引优缺点分析_mysql数据库索引原理,索引优缺点分析

你可能会好奇,取值有023,那取值1是什么意思呢?

我们注意到,在目录项中存储两个字段,分别是最小主键值以及对应的页号,事实上,存放目录项的页与实际存放数据的数据页并无区别,目录项中的两个字段(主键值以及页号)完全可以看作是存储的实际数据,因此,目录项中记录可以称作为目录项记录,即目录页,那InnoDB是如何区别实际数据记录与目录页记录呢,答案就是record_type字段的取值为1来表示目录页记录。

  • 0:普通数据记录,即数据页记录
  • 1: 目录项记录,即目录页记录
  • 2Infimum记录
  • 3Supermun记录

通过上图可以看到,我们使用页30来存放目录页记录,目录页记录与数据页记录的不同点在于:

  • 目录页记录的record_type=1,而数据页记录的record_type=0
  • 目录页记录只包含主键值(索引值)和页号,而数据页记录则可以由用户来自定义,可以包含很多列。

除此之外,两者并无实际的区别,在目录页中,同样可以通过二分法来快速定位到目录页记录,再通过目录页记录向下查询到其对应的数据页。

当然,当目录页中的记录过多,一个目录页难以容纳时,我们可以再多分配一个存储目录项的页即可。当同层存在多个目录项时,我们则可以向上再分配更高一级的目录项,即多级目录。

我们可以观察到,这样多级的目录,不就像一颗树的数据结构了呢?其实,这就是InnoDBB+树结构。

B+树的特点

无论是存放实际数据的数据页,还是存放目录项记录的目录页,都可以把它们放到B+树当中,这些页称为B+树的节点。

其中,存放我们插入的实际数据的记录存放在B+树的最底层节点,这些节点称为叶子节点。其余非叶子节点则用来存放目录项记录。其中B+树最上层的节点称为根节点。

B+树的叶子节点之间是用「双向链表」进行连接,这样的好处是既能向右遍历,也能向左遍历。

2、按物理存储分类
聚簇索引(主键索引)

聚簇索引,又可以称为主键索引,在创建表时,InnoDB会默认为主键创建一棵B+树的主键索引。

聚簇索引的特点在于:

  • 使用记录的主键值大小来对记录和页进行排序。
  • 页(包括叶子节点和非叶子节点)内的记录按照主键值的大小进行排序组成单向链表。页内的记录会被划分为若干的组,每个组中主键值最大的记录在页内的偏移量会被当做该组的槽放在页目录中,以便后续可以通过二分法来查找定位到需要查找的记录。
  • 存放实际数据的各个数据页(叶子节点)同样也根据主键大小排成双向链表。
  • 存放目录数据的各个目录页(非叶子节点)可以分为B+树的多个层级,在同一层级的目录页页根据也中存储的主键值大小来排序成一个双向链表。
  • B+树的叶子节点存放的是完整的数据记录,即保存了该记录的所有列的值。

包含以上两个特点的B+树称为聚簇索引。

非聚簇索引(二级索引)

不同于聚簇索引,非聚簇索引存储的并不是完整的数据,非聚簇索引的叶子节点存放的是指定的索引列+主键值。非聚簇索引的目录页存储的记录中不再是主键+页号的搭配,而是指定的索引列+页号。

以非主键列的大小为排序规则而建立的B+树需要执行回表操作才可以定位到完整的用户记录,这种B+树也称为二级索引或辅助索引。

同样非聚簇索引的B+树的叶子节点也会按照索引列排序并组成双向链表,同一层级的目录页也会根据索引列的大小来排序组成双向链表。

回表

聚簇索引和非聚簇索引的查询有什么区别?

当使用二级索引进行查询定位到符合条件的记录时,当索引中并未存储我们需要查询的字段时(非聚簇索引值的叶子节点只存储主键值以及指定索引值,可能存在需要查询的字段并未存储在索引B+树中),需要根据二级索引中存储的主键值,回表到主键索引查询到对应的字段才能够返回。

根据该记录中的主键信息回到聚簇索引中查找到完整的用户记录。通过携带主键信息到聚簇索引中重新定位完整的用户记录的过程也成为回表

在查询时,回表是有代价的,我们知道,在使用二级索引进行范围查询的时候,二级索引对应的主键值的大小是毫无规律的,每读取一条二级索引记录,就需要根据该二级索引记录的主键值到聚簇索引中执行回表操作,如果对应的聚簇索引记录所在的页面不在内存中,就需要将该页面从磁盘加载到内存中由于要读取很多主键值并不连续的聚簇索引记录,而这些聚簇索引记录分布在不同的数据页中,这些数据页的页号也毫无规律,因此会造成大量的随机I/O。

需要执行回表操作的记录越多,使用二级索引进行查询的性能就越低,因此在某些查询场景下,MySQL优化器宁愿使用全表扫描也不使用二级索引,而选择全表扫描,还是二级索引+回表,这就是查询优化器的工作了

查询优化器会事先针对表中的记录计算一些统计数据,再利用这些统计数据或者访问表中的少量记录来计算回表操作的记录数,如果需要执行回表操作的记录数越多,就越倾向于使用全表扫描,反之则倾向于使用二级索引+回表。

既然回表有一定的代价,那为什么还需要进行回表呢? 在创建索引时直接就完整的数据记录放入索引的叶子节点不就好了么?

如果完整的数据放入到每个索引建立的B+树的叶子节点中确实可以避免回表,但是取而代之的是需要更多的存储空间,太占地方,相当于每建立一棵B+树都需要将所有的完整数据复制一遍。

3、按字段特性分类
主键索引

主键索引,即聚簇索引,建立在主键字段上的索引,通常在创建表的时候一起创建,一张表最多只有一个主键索引,索引列的值不允许有空值。

在创建表时,创建主键索引的方式如下:

CREATE TABLE table_name (

PRIMARY KEY (index_column_1) USING BTREE

唯一索引

唯一索引建立在UNIQUE字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值。

在创建表时,创建唯一索引的方式如下:

CREATE TABLE table_name (

UNIQUE KEY(index_column_1,index_column_2,…)

建表后,如果要创建唯一索引,可以使用这面这条命令:

CREATE UNIQUE INDEX index_name ON table_name(index_column_1,index_column_2,…);

普通索引

普通索引就是建立在普通字段上的索引,既不要求字段为主键,也不要求字段为UNIQUE

在创建表时,创建普通索引的方式如下:

CREATE TABLE table_name (

INDEX(index_column_1,index_column_2,…)

建表后,如果要创建普通索引,可以使用这面这条命令:

CREATE INDEX index_name ON table_name(index_column_1,index_column_2,…);

前缀索引

前缀索引是指对字符类型字段的前几个字符建立的索引,而不是在整个字段上建立的索引。前缀索引可以建立在字段类型为char、varchar、binary、varbinary的列上。

使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率

CREATE TABLE table_name(

INDEX(column_name(length))

建表后,如果要创建前缀索引,可以使用这面这条命令:

CREATE INDEX index_name ON table_name(column_name(length));

4、按字段个数分类
单列索引

当创建索引时,指定某一个字段作为索引列,建立在单列上的索引称为单列索引,比如主键索引。

联合索引

联合索引顾名思义,即指定多个字段列来作为索引列,同时以多个列的大小作为排序规则,即同时为多个列建立索引。

例如建立联合索引(a,b),则在创建的B+树中,记录的排序方式为:

  • 先将各个记录和页按照a列进行排序
  • 在记录的a列相同的情况下,再采用b列进行排序

在联合索引(a,b)的B+树中,存储的内容为:

  • 非叶子节点中,每条目录项记录都有a列、b列、页号3个部分组成。各条记录先按照a列的值进行排序,如果记录的a列相同,则按照b列的值进行排序
  • 叶子节点中,数据页记录由a列、b列和主键列三部分组成;

使用联合索引时,存在最左匹配原则,即按照最左优先的方式进行索引的匹配。

举个例子:

当我们创建联合索引(name, age)时,如果你要查的是所有名字第一个字是"张"的人,你的SQL语句的条件是where name like '张%'。这时这条SQL能够用上这个联合索引。

从上述的例子可以看出,在对name字段进行单列条件查询时,同样能够使用上该联合索引,即联合索引(name, age)可以等效于单列索引(name)

因此,在建立联合索引的时候,如何安排索引内的字段顺序呢?

评估标准是索引的复用能力。如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。例如上述的例子,通过联合索引(name,age)的顺序,可以让我们少维护一个单列索引(name)。当然age

三、索引的优缺点

在了解了B+树的索引结构后,我们知道恰当的索引使用可以为我们带来极大的查询效率,提高性能。但是索引也并非没有缺点,了解好索引的优缺点,选择使用索引时,需要综合考虑索引的优点和缺点,才能让我们在实际使用索引的过程中得心应手。

1、优点
  • 提高数据检索的效率,降低数据库的I/O成本,将随机I/O转变为顺序I/O,这是创建索引最主要的原因;
  • 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性;
  • 由于索引中记录的存储顺序,在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,即帮助服务器避免排序和临时表,降低了CPU的消耗。
  • 可以加速表和表之间的连接,即对于有依赖关系的子表和父表联合查询时,可以提高多表查询的速度。
2、缺点

创建索引和维护索引要耗费时间 ,并且随着数据量的增加,所耗费的时间也会增加。

  • 索引需要占磁盘空间

每个索引在创建后,需要占一定的物理空间存储在磁盘上,因为每建立一个索引,都要为它建立一棵B+树。每一棵B+树的每一个节点都是一个数据页。

一个数据页默认占用 16KB 的存储空间,而一棵很大的B+树由许多数据页组成,这将会占用很大的一片存储空间。

如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。

  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度

当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,即都需要修改各个B+树索引,这样就降低了数据的维护速度。

增删改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行页面分裂、页面回收等操作,以维护节点和记录的排序。

  • 影响优化器执行效率

在执行查询语句前,首先要生成一个执行计划。

一般情况下,一条查询语句在执行过程中最多使用一个二级索引,在生成执行计划时需要计算使用不同索引执行查询时所需的成本,最后选取成本最低的那个索引执行查询,如果此时建了太多的索引,可能会导致成本分析过程耗时太多,从而影响查询语句的执行性能。

四、索引使用场景

索引最大的好处是提高查询速度,但是索引使用不当则会出现上述描述的缺点所带来的影响。因此,索引不是万能钥匙,它也是根据场景来使用的。

那么在什么情况下,我们该使用索引呢?

1、适用索引的场景
  • 字段需要唯一性限制

当业务中某个字段需要唯一性限制时,除了在业务逻辑层面校验,最后一道防线则是通过唯一索引来限制字段唯一。

  • 经常用于WHERE查询条件的字段

对经常用于WHERE查询条件的字段建立索引,能够提高整个表的查询速度,尤其是在数据量大的情况下,创建索引就可以大幅提升数据查询的效率。

  • 经常用于GROUP BYORDER BY的字段

对于经常用于GROUP BYORDER BY的字段,建立索引,利用索引其按索引字段值顺序存储数据的特性,减少排序与临时表带来的损耗。

  • DISTINCT字段创建索引

有时候需要对某个字段进行去重,使用DISTINCT,那么对这个字段创建索引,也会提升查询效率。因为当去重字段创建了索引后是按照顺序递增的,所以在去重的时候会快很多。

  • 在多个字段都要创建索引的情况下,联合索引优于单值索引
2、不适用索引的场景
  • WHERE条件、GROUP BY条件、ORDER BY条件中用不到的字段

索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。

  • 表数据太少的时候,可以考虑不需要创建索引
  • 经常更新的字段不用创建索引

经常更新的字段不用创建索引,比如不要用户余额建立索引,因为索引字段频繁修改,由于要维护 B+Tree的有序性,那么就需要频繁的重建索引,这个过程是会影响数据库性能的。

本文来自 Go就业训练营 小韬同学的投稿。

又出成绩啦

我们又出成绩啦!大厂Offer集锦!遥遥领先!

这些朋友赢麻了!

这是一个专注程序员升职加薪の知识星球

答疑解惑

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

79891366)]
[外链图片转存中…(img-YhGAbkHq-1713079891367)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-XpRAjYz5-1713079891367)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值