索引概述

45 阅读16分钟

索引

MySQL官方对索引的定义为:索引是帮助MySQL高效获取数据的数据结构。

索引的本质:索引是数据结构,可以简单理解为“排好序的快速查找数据结构”,满足特定的查找算法,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法

索引是在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎不一定支持所有索引类型。同时存储引擎可以定义每个表的最大索引数和最大索引长度,所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节,有些存储引擎支持更多的索引数和更大索引长度

优点

提高数据检索效率,降低数据库的IO成本,这也是创建索引最主要的原因

创/建唯一索引,可以保证数据库表中每一行数据的唯一性

在实现数据的参考完整性方面,可以加速表和表之间的连接,也就是说,对于有依赖关系的子表和父表联合查询时,可以提高查询速度。

在使用分组和排序子句进行数据查询时,可以显著减少分组和排序的时间,降低CPU的消耗

缺点

增加索引也有许多不利的方面,主要表现在如下几个方面:

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

索引需要占用磁盘空间,除了数据表占用数据空间外,每一个索引还要占用一定的物理空间,存储在磁盘上,如果有大量索引,索引文件就可能比数据文件更快达到最大文件尺寸

虽然索引大大提高了查询速度,同时却会降低更新表的速度,当对表中的数据进行增删改的时候,索引也要动态维护,这样就降低了数据的维护速度

提示:

索引可以提高查询速度,但是会影响插入记录的速度,这种情况下,最好的办法就是先删除表中的索引,然后再插入数据,插入完成后再创建索引。

常见的索引概念

索引按照物理实现方式,索引可以分为2种:聚簇(聚集)索引和非聚簇(非聚集)索引。也把非聚簇索引称为二级索引或者辅助索引

聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(所有的用户记录都存储在了叶子节点),也就是所谓的索引即数据,数据即索引

“聚簇”表示数据行和相邻的键聚簇存储在一起

特点:

使用记录主键值的大小进行记录和页的排序:

页内的记录是按照主键的大小顺序排成一个单向链表

各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表

存放目录项记录的页分为不同的层次,在同一层次的页也是根据页中目录项记录的主键大小顺序排成一个双向链表

B+树的叶子节点存储的是完整的用户记录(指这个记录中存储了所有列的值,包括隐藏列)

我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处,这种聚簇索引并不需要我们在MySQL语句中显示地使用INDEX语句去创建,InnoDB存储引擎会自动为我们创建聚簇索引

优点:

数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快

聚簇索引对于主键的排序查找和范围查找速度非常快

按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据

限制:

对于MySQL数据库目前只有InnoDb数据引擎支持聚簇索引,而MyISAM并不支持聚簇索引

由于数据物理存储排序方式只能有一种,所以每个MySQL的表只能有一个聚簇索引,一般情况下就是该表的主键

如果没有定义主键,InnoDB会选择非空的唯一索引代替,如果没有这样的索引,InnoDB会隐式的定义一个主键来作为聚簇索引

为了充分利用聚簇索引的聚簇特性,所以InnoDB表的主键列尽量选用有序的顺序id,而不建议使用无需的id,比如UUID,MD5,SASH,字符串列作为主键无法保证数据的顺序增长

二级索引(辅助索引、非聚簇索引)

聚簇索引只能在搜索条件是主键值时才能发挥作用,因为B+树中的数据都是按照主键进行排序的,那如果我们想以别的列作为搜索条件该怎么办?

答案:可以多建立几颗B+树,不同的B+树中数据采用不同的排序规则,比如说:使用c2列的大小作为数据页,页中记录的排序规则

二级索引所建立的B+树和聚簇索引有几处不同:

使用记录c2列的大小进行记录和页的排序,这包括三个方面的含义:

页内的记录是按照c2列的大小排序排成一个单向链表

各个存放目录项记录的页也是根据页中记录的c2列大小顺序排成一个双向链表

存放目录项记录的页分为不同层次,在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个双向链表

B+树的叶子节点存储的并不是完整的用户记录,而只是c2列+主键这两个列的值

目录项记录中不再是主键+页号的搭配,而变成了c2列+页号的搭配

所以如果现在想要通过c2列的值查找某些记录的话就可以使用非聚簇索引建立好的B+树

回表:

根据这个c2列大小排序的B+树只能确定我们要查找的记录的主键值(这颗B+树的叶子节点只存储了c2列的值和主键的值),所以如果我们想根据c2列的值查找到完整的用户记录,仍然需要到聚簇索引中再查一次,这个过程称为回表

问题:为什么还需要回表操作,直接把完整的用户记录也放在叶子节点可以吗?

回答:

如果把完整的用户记录放到叶子节点是可以不用回表,缺点是太浪费空间了,相当于每次建立一颗B+树都需要把所有用户记录都再拷贝一次,因为这种按照非主键列建立的B+树需要一次回表操作才可以定位完整的用户记录,所以这种B+树也被称为二级索引或者辅助索引,由于我们使用的是c2列的大小作为B+树的排序规则,所以我们也称这个B+树是为c2列建立的索引

非聚簇索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个非聚簇索引

聚簇索引和非聚簇索引的原理不同,在使用上也有一些区别:

聚簇索引的叶子节点存储的就是我们的数据记录,非聚簇索引的叶子节点存储的是数据位置,非聚簇索引不会影响数据表的物理存储顺序

一个表只能有一个聚簇索引。因为只能有一种排序存储的方式,但是可以有多个非聚簇索引,也就是多个索引目录提供数据检索。

使用聚簇索引的时候,数据的查询效率高,但如果对数据进行插入、删除、更新等操作,效率会比非聚簇索引低

联合索引

我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照c2和c3列的大小进行排序,这个包含两层含义:

先把各个记录和页按照c2列进行排序

在记录的c2列相同的情况下,采用c2列进行排序

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

1.跟页面位置不动

每当为某一个表创建一个B+树索引(聚簇索引不是人为创建,默认就有)的时候,都会为这个索引创建一个根节点页面。最开始表中没有数据的时候,每个B+树索引对应的根节点中既没有用户记录也没有目录记录

随后向表中插入用户记录时,先把用户记录存储到这个根节点中

当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页a中,然后对这个新页继续页分裂的操作,得到一个新页,比如页b,这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页a或页b中,而根节点便升级存储目录项记录的页

一个B+树索引的根节点自诞生后,就不会再移动,这样只要对表建立一个索引,那么他的根节点的页号就会被记录到某个地方,然后凡是InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引

2.内节点中目录项记录的唯一性

B+树索引的内节点中目录项记录的内容是索引列+页号的搭配,但是这个搭配对于二级索引来说有些不严谨,如果二级索引中目录项记录的内容只是索引列+页号的搭配,那就可能会出现重复的记录

为了保证在B+树的同一层内节点的目录项记录除页号这个字段以外是唯一的,对于二级索引的内节点的目录项记录的内容实际上是由三部分构成的:索引列的值、主键值、页号

也就是说,把主键值也添加到二级索引内节点中的目录项记录了,这样就能保证B+树每一层节点中各条目录项记录除页号这个字段外是唯一的(因为主键是唯一的)

3.一个页面至少存储2条记录

一个B+树只需要很少的层级就可以轻松存储亿条记录,查询速度相当不错,这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录,那如果一个大的目录只存放一个子目录,就会导致目录层级非常多,而且最后那个存放真实数据的目录也只能存放一条记录,所以InnoDB的一个数据页至少可以存放两条记录(至少得形成树的结构)

MyISAM中的索引方案

MyISAM引擎使用B+Tree作为索引结构,叶子节点的data域存放的是数据记录的地址

MyISAM索引的原理

在InnoDB中索引即数据,也就是聚簇索引的那颗B+树的叶子节点中已经把所有完整的用户记录都包含了,而MyISAM的索引方案虽然也使用树形结构,但是却将索引和数据分开存储

将表中的记录按照记录的插入顺序,单独存储在一个文件中,称为数据文件(.MYD),这个文件并不划分为若干个数据页,有多少记录就往这个文件中塞多少记录就行,由于插入数据的时候并没有刻意按照主键大小排序,所以我们并不能在这些数据上使用二分法进行查找

使用MyISAM存储引擎的表会把索引信息另外存储在索引文件(.MYI)中,MyISAM会单独为表的主键创建一个索引,只不过在索引的叶子节点中存储的不是完整的用户记录,而是主键值 + 数据记录地址的组合

MyISAM中索引检索的算法为:首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址读取相应数据记录

MyISAM与InnoDB对比 MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引不同

在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表,意味着MyISAM的存储的全部都是二级索引

InnoDB的数据即索引,都是放在一个文件中的,而MyISAM中的数据和索引是分开存储的,索引文件仅存储了数据的地址

InnoDB的非聚簇索引data域存储相应记录主键的值,而MyISAM索引记录的是地址,InnoDB所有非聚簇索引都引用主键作为data域

MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中去取数据,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然也不慢,但还是比不上直接用地址去访问

InnoDB要求表必须有主键(MyISAM可以没有),如果没有显示指定,MySQL系统会自动选择一个非空且唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6字节,类型为长整型

小结:

为什么不建议使用过长的字段作为主键?

答:因为所有二级索引都引用主键索引,过长的主键索引会导致二级索引变得过大,然而一个数据也的大小是有限的(16K)如果主键过长,就会导致一个数据也存放的数据就更少,那B+树的层级就可能会更高,查询效率就低

非单调的字段作为主键在InnoDB中是不推荐的

答:因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键是一个很好的选择

八股文

B+树的存储能力如何?为什么说一般查找行记录最多只需1~3次磁盘IO?

InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(估值,这里取1K=1000),也就是说深度为3的B+Tree索引可以维护 1000 1000 1000 = 10亿条记录(这里假设一个数据页也存储1000条行记录数据)

实际情况中每个节点可能不能填满,因此在数据库中,B+Tree的高度一般都在2至4层,MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘IO操作

Hash索引与B+树索引的区别

Hash索引不能进行范围查询,而B+树可以,这是因为Hash索引指向的数据是无序的,而B+树的叶子节点是个有序的链表

Hash索引不支持联合索引的最左原则(即联合索引的部分索引无法使用),而B+树可以,对于联合索引来说,Hash索引在计算Hash值的时候是将索引键合并后再一起计算Hash值,所以不会针对每个索引单独计算Hash值,因此如果要用到联合索引的一个或几个时,联合索引无法被利用

Hash索引不支持ORDER BY排序,因为Hash索引指向的数据是无序的,因此无法起到排序优化的作用,而B+树索引数据是有序的,可以起到对该字段ORDER BY排序优化的作用,同理,我们也无法使用Hash索引进行模糊查询,而B+树使用LIKE进行模糊查询的时候,LIKE后面后模糊(比如%结尾)查询的话就可以起到优化作用

InnoDB和MyISAM是不支持哈希索引的,默认采用B+树索引,InnoDB提供的自适应Hash是不需要手动指定的,如果是Memory/Heap和NDB存储引擎,是可以进行选择Hash索引的

补充:InnoDB提供自适应Hash索引,是在某个数据经常被访问,当满足一定条件的时候,就会将这个数据页的地址存放到Hash表中,这样下次查询的时候,就可以直接找到这个页面所在位置,这样让B+树也具备了Hash索引的优点