索引

概念

官方话:索引是帮助MySQL高效获取数据的数据结构。

  • 为了能够使存储引擎快速找到数据记录的一种数据结构,就相当于是一本书的目录部分,目的是为了减少磁盘的IO次数,避免了全表扫描,加快查询速率。
  • 索引是在存储引擎中实现的,InnoDB底层索引使用的是B+Tree的数据结构,不同的存储引擎使用的数据结构可能不一样。同时存储引擎可以定义每个表的最大索引数最大索引长度

优点

  • 降低数据库的IO成本。
  • 保证数据库表中的每一行的唯一性。
  • 在实现数据的参考完整性方面,可以加速表和表之间的连接。对有依赖关系的子表和父表联合查询时,可以提高查询速度。
  • 在使用分组和排序子句进行查询时,可以显著的减少分组和排序的时间,降低了CPU的消耗。
  • 因为索引本质上就是一种排好序的快速查找数据结构,就能提高分组和排序效率。

缺点

  • 创建索引和维护索引需要耗费时间,数据量越多,消耗时间越多。
  • 索引需要占用磁盘空间,存储在磁盘上。
  • 索引虽然提高了查询速度,但是降低了更新表的速度,因为更新表和删除需要动态更新维护索引,就降低了维护速度。
  • 解决办法就是,更新之前直接全部删了,更新表之后再重新创建索引,虽然会消耗时间,但是效率会比动态维护索引要高一点。

InnoDB索引推演

数据页:存储引擎从磁盘中读取数据是以数据页为单位进行读取的,一个数据页能存放16kb大小的数据。

精确匹配例子:

SELECT [列名列表] FROM 表名 WHERE 列名 = XXX;

在一个页中的查找

目前表中数据量较小,所有记录可以被存在一个页中,就可以根据搜索条件的不同分为两种情况:

  • 以主键为搜索条件
  • 使用二分法快速找到对应记录,因为主键一般是自增有序的。
  • 以其它列为搜索条件
  • 用其他列的话就只能是从最小记录开始依次遍历,效率较低。

在多个页中的查找

假设有很多条数据,存在很多个数据页来记录,此时查找数据就有两个步骤:

  • 首先得找到这个数据所在的对应数据页。
  • 再从这个数据页中找到想要的数据。

那么问题就来了,如果数据量非常非常大,比如有1000000条数据,那么就会有很多个数据页,而数据页之间是不存在有规律的,无法快速定位到目标数据在哪个数据页上,此时有两种办法:

  • 死办法,从第一个数据页挨个遍历,沿着双向链表往下找,遍历所有数据页。
  • 这下就可以用到索引了,索引就很好的解决了这个问题。

MySQL 索引结构 mysql中索引的数据结构_主键

举个图书馆的例子,每个书架就相当于数据页,而你要找的书就相当于要找的数据,如果没有索引(书架边上的索引号),你就只能一个个书架,一本本书去翻,而有了索引你就能快速去对应书架对应行找到你想要的。

MySQL 索引结构 mysql中索引的数据结构_MySQL 索引结构_02

再来看一个简单的索引设计方案,这里要说一下,在磁盘中数据和数据页的存放地址不一定是连续的,是无规则存放的,因此记录和记录之间是采用单链表的数据结构存储,而数据页和数据页之间是采用双向链表的方式存储。

  • 每个数据页中的记录主键一定是下一个页的要大于之前页的主键,如果存入的数据不满足这个特点,就会发生数据记录移动,使其按主键排序,这个移动的过程就叫做页分裂
  • 如果我们没有索引,就得一个个数据页一条条记录去遍历,因此,设计一个简单的索引,即目录项。
  • 页的记录中最小的主键值,用key表示。
  • 页号就用page_no来表示。
  • 有了这个目录就可以通过主键的值快速找到对应的数据页,同时也可以对目录项进行二分查找,来取代遍历索引的操作,进一步提高效率。

PS:以上这个设计方案讲的是基于主键的索引设计!

迭代1次:目录项记录的页

MySQL 索引结构 mysql中索引的数据结构_数据库_03

由于如果目录项每个记录之间用物理连续的方式存储的话,那么如果更新数据,插入数据、删除数据等操作都会导致后面的数据要往前移动,索性就干脆也用数据页的方式保存目录项,目录记录之间用单链表保存,那么怎么和用户数据区分呢,关键就在于record_type字段,1位目录,0为用户记录。每个数据页都会维护一个页目录,用于二分查找快速找到数据。

迭代2次:多个目录项记录的页

如果目录项记录多到一个页不够用了,那就再生成一个页来保存,就存在了多个页保存目录

MySQL 索引结构 mysql中索引的数据结构_mysql_04

迭代3次:目录项记录页的目录页

如果目录项记录页也很多了,找起来效率也比较低了,就可以给这些目录项记录页设置一个目录页,相当于大目录嵌套小目录,小目录里查询对应的数据,能减少IO次数,提高效率。

MySQL 索引结构 mysql中索引的数据结构_数据结构_05

至此,我们的整个目录页和对应数据的关系就可以用一个数据结构来模拟,这也是B+树的大致样子:

MySQL 索引结构 mysql中索引的数据结构_主键_06

叶子节点就是我们普通用户记录的数据页,非叶子结点就是目录页了。

从图上来看B+树的层次越低,IO的次数也就越少,我们实际开发中的B+树一般不会超过四层,因为四层已经可以存储非常非常多的记录了。

常见索引

索引根据物理实现方式,可以分为2种:聚簇索引和非聚簇索引(二级索引、辅助索引),也就是以是否以主键为标准构建来区分。

聚簇索引

聚簇索引不是一种单独的索引类型,而是一种数据存储方式,也就是所谓的索引即数据数据,数据即索引,上面所介绍的B+树都是聚簇索引。

另外,我们不需要再MySQL语句中显式的使用INDEX去创建聚簇索引,InnoDB会自动为我们创建。

优点

  • 访问数据更快,因为数据就在索引中。
  • 对于主键的排序查找和范围查找速度快。
  • 节省了大量的IO操作。

缺点

  • 插入速度严重依赖于插入顺序,如果插入不按照主键的顺序插入的话,容易出现页分裂,影响性能,因此InnoDB中一般都会设置一个自增的主键。
  • 更新主键代价很高,会导致被更新的行移动,因此主键一般不可更新。
  • 二级索引访问需要两次索引查找,第一次找主键,第二次根据主键查找行数据。

限制

  • 目前只有InnoDB中有聚簇索引,其它不支持。
  • 每个表只能用一个聚簇索引,一般就是主键。
  • 没有定义主键的话就会选择非空的唯一索引代替,还没有的话就会定义一个隐式的主键。
  • 尽量选择有序的顺序id作为主键。
二级索引

聚簇索引是根据主键进行搜索的,那如果我们的搜索条件不是主键而是其它的字段列呢?

此时就需要以其它的字段列再构建一颗B+树,也就是二级索引。

整体上和聚簇索引长得差不多,主要有以下区别:

  • 二级索引是以非主键列名为序构建的B+树。
  • 叶子结点存放的不是数据,而是数据的位置。
  • 如果需要返回的字段不止是搜索条件这一个字段,就需要根据查出来的主键进行回表操作,到聚簇索引中继续查询其他字段。
  • 非聚簇索引是可以有多个的,聚簇索引只能有一个。
  • 插入删除更新的效率非聚簇索引效率更高。

MySQL 索引结构 mysql中索引的数据结构_数据结构_07

联合索引

也就是以多个列的字段为搜索条件构建的索引,称为联合索引。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cA8zq7oe-1676467383581)(null)]

先以第一个列的字段排序,再依次以后面的字段排序,构成一颗B+树,本质上是一个二级索引。

InnoDB中B+树索引的注意事项

  • 根页面的位置万年不动
  • 这是什么意思呢,上面介绍的B+树为了方便理解是由下往上依次创建的,而实际上B+树的生成不是这样的。
  • 首先会有一个根节点,用来存放所有的用户记录,如果当前页存不下这么多数据了,就会新开一个页(子节点),将当前节点的数据存入子节点,根节点变为目录页,目录页一个不够用的时候,又会生成新的目录页子节点,同样复制过去,根节点变为目录页的目录页,以此类推,根节点永不会变。
  • 内节点中目录项记录的唯一性
  • 非叶子结点如果采用的是索引列+页号的搭配的话,如果多条记录列的值相同,就不能判断该插入哪个页了,因此就会选择索引列+主键+页号的方式进行唯一性保证。
  • 一个页面最少存储2条记录