前言
嗨,大家好,我是田哥。
在工作中我们常常用到索引,无论是普通索引还是唯一索引,都是一些常用的索引方式,目的就是为了提高查询效率,避免业务请求超时等问题。那么,当你在使用索引的时候,是否思考过以下问题:索引是如何实现的,以及一共有哪些索引呢?
带着这些问题,让我们开始吧~
什么是索引
不知道你第一次听到索引二字,是否会觉得它非常抽象,且难以理解。索引,什么是索引呢?
索引其实就是一种数据结构。那么在计算机世界中,任何数据结构都有它的用途,如栈、堆、树、队列等。那么,数据库又什么要使用索引?为了提高查询(检索)效率的啦。所以,它就如同书本最前面的目录一样,作用就是作为检索和排序,来极大程度提升你对数据的查询速度。
所以,你可以理解索引是帮助存储引擎高效获取数据的一种方式,就如同你为了高效获取书中的某一块知识而去看书本目录一样。
索引的实现方式
索引通常使用的是叫做B树(B Tree)和B+树(B+ Tree)的数据结构。也会使用哈希(Hash)作为索引,这几种方式我们都会聊聊。
B树
B Tree 指的就是 Balance Tree,也就是平衡树。说说它的特点:
- 它是一颗多路查找树,并且所有叶子节点位于同一层
- 每个节点中不仅包含数据的键值,还有data值
- 每个节点都相当于一个磁盘块
你可以对它进行存储数据、对其进行排序,由于是顺序进行读取、插入和删除,所以它的时间复杂度为O(log n)。
B+树
B+ Tree是基于B Tree实现的。它具有B Tree的平衡性,并且由于是有序的,也可以通过指针来进行顺序访问,时间复杂度依然为O(log n):
说说它的特点:
- 每个叶子节存储索引字段的键值及字段对应的数据
- 非叶子节点只存储索引字段的键值及指向子节点的指针,不存储数据
- 每个结点相当于一个磁盘块
- 同一层级的叶子节点之间以双向链表的形式相连
那么,为什么有了B树,还需要B+树呢?
其实,B树和B+树的最大区别就在于:B+树的非叶子节点只会存储指向下一个节点的地址,它并不存储实际的值。并且它所有的叶子节点之间都是使用链表进行连接,由于是有序的,这样进行遍历查找会更快。
所以,B+树就具备了B树不具备的优点。由于非叶子节点不存储数据,在同样空间的内存页当中,就可以存放更多的key,这些key紧密、顺序地排列,对于数据的查询来说会比B树更加稳定和迅速,因为它能够更好地利用空间局部性原理。
Hash
除了B树和B+树,还有一种索引就是哈希了。哈希索引就是基于哈希算法,对于每一行数据,数据库存储引擎会对所有索引列都通过哈希算法去计算一个哈希码,然后将这个哈希码存储在哈希索引中,由于使用的是哈希算法,所以使用哈希索引就会存在两个弊端:
- 哈希算法计算出来的哈希值可能存在哈希冲突
- 由于计算出来的是个值所以无法进行范围查询
所以,B树和B+树是数据库中比较常用到的两种索引数据结构,而对于MySQL InnoDB存储引擎来说,它使用的是B+树。
索引的分类
我们刚刚讲解的是索引的实现方式,那么对于索引还能将它以实际的应用功能划分和按照物理实现来进行划分。
按照功能划分
我们平时在写SQL或者建表的时候,会根据实际的业务查询频率、特点和数据量为字段建立各种各样的索引。那么这些索引可以根据实际应用来进行分类:
- 主键(PRIMARY KEY)
- 唯一索引(UNIQUE)
- 普通索引(INDEX)
- 全文索引(FULLTEXT)
主键
在建表的时候只要指定了主键,就会生成对应的索引。
为什么我这里说主键,而不说主键索引呢。很多人将主键和唯一索引、普通索引这些挂钩在一起,称之为“主键索引”。其实这是不准确的。因为主键本质上来说,它是一种约束,而索引是一种数据结构,用来提升查询效率。两者不是一个层面的东西,也有着功能上质的区别。
在这里我将主键根据功能划分将它放进来,因为它是这些索引 、甚至是一张表的基础,对于MySQL的InnoDB存储引擎来说,一个表结构一定要有一个主键,表中的数据都是按照主键顺序进行存放的。其它索引,也都要依赖于主键来生成索引。
主键的创建方式:
- 方式1:创建表的时候指定主键,如果创建表时没有显示定义主键,InnoDB就会通过以下规则去创建一个主键:
判断表中是否存在一个非空的唯一索引。如果存在,则该索引对应的字段名即为主键。如果不存在,则InnoDB会自动创建索引
- 方式2:通过SQL语句:
ALTER TABLE `table_name` ADD PRIMARY KEY `column_name`;
那们,我们常常说的“主键索引”又是怎么一回事呢?一会我们就会接触到。
唯一索引
唯一索引就是作为一种索引,可以确保这行索引列不包含重复的值。所以它和非唯一索引的区别就是除了具有索引的功能,还限制这一字段在数据库中不能有重复记录,我们可以通过以下SQL创建唯一索引:
ALTER TABLE `table _name` ADD UNIQUE indexName;
普通索引
朴实无华,最普通的索引类型了。将一个或者多个字段添加索引,仅仅用来增加查询速度。fancy在工作中创建最多的就是普通索引了。它的创建语句:
ALTER TABLE `table_name` ADD INDEX index_name;
全文索引
作为程序员,我们每日都要和搜索引擎打交道了。不仅仅是搜索引擎,在很多app上,都有一个全文搜索的功能:
如果不去使用一些中间件技术,我们的每一次搜索,都要去根据关键词去数据库中查找。
如果不使用索引,那么在海量的数据里面要将你预期的结果筛选出来,可能要花费好几秒的响应时间。这对于用户来说无疑是不可接受的。
所以,全文索引,就适合应用于这样的业务场景。
如果想要使用全文索引,就必须指定存储引擎为MYISAM,因为InnoDB存储引擎是不支持全文索引的。
由于全文索引存在许多弊端,比如不支持大小写、索引创建慢等问题,所以不建议使用MySQL的全文索引。在现在的场景中,我们一般使用ElasticSearch这种封装好的中间件来满足全局数据检索的需求。全文索引创建的方式:
ALTER TABLE `table_name` ADD FULLTEXT `column`;
按照索引数量划分
如果一个索引加在一个字段上,那么它就是一个单列索引。但是我们也可以将多个字段联合起来一起创建一个索引,这种索引就被称为“联合索引”,也叫多列索引或者组合索引。为了方便起见。我们之后都统一叫做“联合索引”。
注:多个单列索引并不能被称为组合索引
之后,我们会单独地详细讲讲这个联合索引,以及它产生的一些问题,所以本文先不过多描述。
按照物理实现划分
为了帮助大家更好理解,我们现在创建一张表:
create table fancyFamily
(
id int(11) not null auto_increment comment '家庭成员ID',
name varchar(16) not null comment '家庭成员名字',
age int(11) default null comment '家庭成员年龄',
primary key (id)
) engine InnoDB
AUTO_INCREMENT = 10
default charset = utf8;
然后插入一定的数据量:
insert into fancyFamily(id, name, age) values (1, 'fancy', 26);
insert into fancyFamily(id, name, age) values (3, '小黄', 25);
insert into fancyFamily(id, name, age) values (8, '大粉', 50);
insert into fancyFamily(id, name, age) values (10, '大黄', 55);
insert into fancyFamily(id, name, age) values (13, '小蓝', 18);
insert into fancyFamily(id, name, age) values (16, '小白', 15);
聚集索引
什么是聚集索引?我们刚刚说过,对于一颗以B+树为数据结构创建的索引,只有叶子节点中存放数据,并且叶子节点中的数据都是按照数据库表中的数据一行一行进行连续排列的:
我们就将一颗满足于以上数据存储形式的索引形式,称之为“聚集索引”。这里注重的是数据的存储形式,也就是将数据存储在叶子节点中。
并且由于一张表中的数据无法存放于两颗不同的B+树中。所以一张表也只能有一个聚集索引。
那我们现在就存在一个问题了:一张表中只能有一个主键,也只能有一个聚集索引,并且聚集索引还是按照B+树中主键的数据存放形式来生成的,那么聚集索引不就是主键吗?
还是上文提到的知识点:主键是一种约束,用来完善表结构的数据完整性。而聚集索引是一种索引,目的就是将数据建立成有序的以便于减少查询的时间复杂度。它是一种索引,而索引的用途就是用来提升查询效率的。就像是你买房和买车一样,它们之间的用途就有着本质的区别。
那“主键索引”这个词,又是怎么一回事呢?在InnoDB存储引擎中,一张表一定会存在一个主键,它是索引能够被用来提升效率的基础,只有拥有主键才能进行排序和查询。而只有建立了主键,才能够生成聚集索引这种数据存储形式,所以当一张表的主键生成式,其对应的聚集索引也便生成了。所以有人也喜欢将聚集索引称为“主键索引”。
辅助索引
有了聚集索引,就会有非聚集索引。除了聚集索引之外的索引都叫做非聚集索引,比如以某非主键的字段创建索引后构建起来的索引树。它也叫二级索引或者辅助索引,为了方便起见,我们之后都将其称为辅助索引。
那么它和聚集索引的区别是什么呢?最大的区别就是聚集索引的叶子结点是包含表结构的全部行数据的,但是辅助索引并不包含。
它依然使用B+树,只是每个叶子节点存储的是聚集索引所在列的主键值:
不同于聚集索引,由于其不按照主键进行排列检索,所以一张表中可以存在多个主键索引。
那么,如果我们现在创建了name这个辅助索引,并且去查询小黄这个名字,它会怎么操作呢?
select * from workers where name='小黄';
它的查询步骤如下:
- 通过name=’小黄‘这个条件去辅助索引找到对应的叶子节点的键值
- 找到'小黄'对应的数据,也就是小黄这行数据对应的主键
- 通过这个主键,返回聚集索引
- 通过聚集索引的主键排序,去叶子节点找到主键ID为3的小黄对应的数据
也就是说,如果使用辅助索引,它不会像主键索引那样直接去叶子节点找对应的数据,而是先去叶子节点找到对应条件的主键,再返回聚集索引根据这个主键去搜索一遍。这种情况我们称为回表:
也就是说,如果使用了辅助索引,是要比聚集索引多一次树的访问和遍历的。
总结
以上我们描述了什么是索引、索引的实现方式、和索引的具体分类。通过索引的功能、数量、和物理实现可以区分为不同的索引,也具体描述了聚集索引和非聚集索引的区别。
在我身边有工作了很多年的同事,他们业务能力有的一级棒,但是在讲到数据库的索引也会出现不够清晰的划分和总结,所以我觉得将这些基础知识理清十分重要。