MySQL索引
引言
索引本质是一种数据结构,且在大多数情况下使用B+树,但可能也会用到哈希等数据结构,其作用是快速查找数据,可以做一个简单实验,使用一台低配置的云服务器,在一个具备8百万行数据的表中查找一个数据,未使用索引时,耗时5min+,而使用索引时,耗时0.01s
1. 索引的基础原理
1.1 磁盘结构
先来提一下磁盘,磁盘也就是传统硬盘,即断电后也能存储数据的硬件,传统磁盘每个扇区一般都是512字节,现代磁盘的单个扇区可容纳4kb数据
根据冯诺依曼体系,我们知道,对一个数据进行操作时,需要先将数据从硬盘读取到内存中,然后cpu进行相关操作,那么,对于海量数据,在这个过程中,是将数据截成小块,多次IO操作效率高,还是直接读取整个数据块效率高呢?答案是直接读取整块数据,也就是说,读取数据时,需要尽可能减少IO次数,才能提高效率
传统磁盘的一个扇区,可存储4KB数据,而mysqld(InnoDB 引擎)中,一个查询页为16KB,也就是说,当使用MySQL查找数据时,会将磁盘中的4个扇区的数据读取到内存,并创建一个page,也就是说,就算仅仅需要查询一个数据,也会读取16kb的数据,这又是为什么呢?答案是,根据局部性原理,读取一个数据时,很有可能也需要周围的数据,因此,为了减少IO的次数,mysqld索性直接读取16kb的数据 -》对于海量数据,查找时,mysqld并不会将这些数据全部读取到内存中,而是会将这些数据进行分组,需要时,读取几个需要的page(单个page大小为16kb)即可
1.2 索引原理
那么,mysqld如何快速查询所需page呢?这就与索引有关了,索引,简单来说,其实就类似书的目录,而想要了解这个目录是如何构成的,就要来说说上面提到的B+树了,B+树,本质是一棵多叉树,其叶子节点中存储数据 + 数据中用于快速查找的关键字(目录),中间节点中仅存储下层节点中关键字的目录(详见下面两种索引图)
注:若数据海量,可能会迫使B+树具备多层节点,但总体而言,B+树的形状仍旧是矮胖形的(相比红黑树,AVL树等)
需要重点记忆的是,使用索引进行数据的查询时,不会将整棵B+树全部读取到内存中,而是需要哪个节点,就读取哪个到内存,这样做既减少了IO次数,也减少了内存使用,能够大大提高查询效率,需要补充说明的一点是,B+树的叶子节点相互连接的结构,能够帮助数据的范围查找,而且由于B+树是矮胖型结构,也能减少IO次数,提高效率,这些优势也算是索引选择B+树的主因
1.3 两种常用索引------聚簇索引与非聚簇索引
对于一张表,是可能会创建多个索引的,且每创建一个索引,就会新增一棵B+树,但需要注意,对于不同的mysqld存储引擎,其索引使用B+树的方式可能不同,常见的有聚簇索引和非聚簇索引两种,比如InnoDB存储引擎,其索引方式就为聚簇索引,而MyISAM存储引擎则采取了非聚簇索引
1.3.1 聚簇索引
聚簇索引:聚簇索引机制中具备主键索引与二级索引两种索引,对于主键索引,会创建主键索引B+树,会将完整数据 + 主键 + 对应的主键索引的数据目录(主键索引关键字)全部存放到该树的叶子节点中;而对于二级索引,则会创建二级索引用B+树,其叶子节点中仅会存放对应的索引键 + 二级索引的目录 + 数据主键,使用二级索引查找数据时,会先在二级索引的B+树中根据二级索引查找出对应的主键,然后再根据主键从主键索引的B+树查找出完整数据,此过程被称为回表查询
聚簇索引结构如下:

1.3.2 非聚簇索引
非聚簇索引:对于每个索引,都会创建一棵叶子节点信息为数据的指针 + 索引键 + 索引键目录的B+树,之后使用索引进行查询时,仅需找到对应索引的B+树,然后通过索引键值查询出所需数据的指针,再根据指针找到对应数据即可
非聚簇索引结构如下:

2. 索引的使用
索引宏观上来看可以分为主键索引和非主键索引两种,而非主键索引大致可以分为唯一键索引,普通索引与全文索引(选学)三种,下面就详细介绍几种索引的使用
2.1 主键索引的创建
主键索引可通过三种方式进行创建:
-
在创建表的时候,直接在字段名后指定 primary key即可自动创建主键索引
create table user1(id int primary key, name varchar(30)); -
在创建表的最后,指定某列或某几列为主键索引(即创建复合主键的方式,同时也会创建复合主键索引)
create table user2(id int, name varchar(30), primary key(id)); -
创建表以后再添加主键
alter table table_name add primary key(id);
添加主键索引的示例:

主键索引具备如下特点:
- 一个表中,最多有一个主键索引,当然可以使用复合主键
- 主键索引的效率高(原因是数据不重复,创建的B+树效率高)
- 创建主键索引的列,它的值不能为null,且不能重复(主键约束,当然不能为null)
- 主键索引的列基本上是int(因为一般都会将自增约束的列作为主键)
2.2 唯一索引的创建
唯一索引可通过三种方式进行创建:
-
在表定义时,在某列后直接指定unique唯一属性,mysqld便会自动为唯一约束创建唯一索引
create table user4(id int primary key, name varchar(30) unique); -
创建表时,在表的后面指定某列或某几列为unique,mysqld便会自动为唯一约束创建唯一索引
create table user5(id int primary key, name varchar(30), unique(column_name)); -
创建好table后进行唯一索引的添加
alter table table_name add unique(column_name);
添加唯一键索引示例:

唯一索引具备如下特点:
- 一个表中,可以有多个唯一索引
- 查询效率高(因为key是唯一的)
- 如果在某一列建立唯一索引,必须保证这列不能有重复数据
- 如果唯一索引字段同时设置
not null,可以实现和主键一样「唯一 + 非空」的约束效果,但它依旧不是主键,一张表可以存在多个这类索引
2.3 普通索引的创建
普通索引可通过三种方式进行创建:
-
在表的定义最后,指定某列为索引
create table user8(id int primary key,name varchar(20),email varchar(30),index(column_name) ); -
创建完表以后指定某列为普通索引
alter table table_name add index(column_name); -
创建一个索引名为 idx_name 的索引
create index idx_name on table_name(column_name);
添加普通索引示例:

需要注意的是,MySQL并不支持索引建立后重命名操作,只能通过删除原索引 + 新建的方式实现该需求
普通索引具备如下特点:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
2.4 索引的查询与删除
查询索引的方法也有三种:
- show keys from table_name;
- show index from table_name;
- desc table_name;(信息简略)
查询示例:

其中,key_name 字段为索引名,Column_name 字段为索引包含的字段
删除索引的方法也有三种:
- 删除主键索引: alter table 表名 drop primary key;
- 其他索引的删除: alter table 表名 drop index 索引名; 索引名就是show keys
from 表名中的 Key_name 字段 - drop index 索引名 on 表名
删除主键索引示例:

删除普通索引示例:

2.5 复合索引与索引的创建原则
复合索引:
上面也提到了,类似约束的创建般,单个索引也可以包含多列,并且,复合索引在使用时有些需要注意的点:
当查询的数据均在复合索引中,且也是通过该复合索引进行查询时,不会发生回表查询行为,此特点大大提高了该类情况的查询效率,该行为被称为覆盖索引(注:实际上单索引也可能出现覆盖索引)使用复合索引查询时,WHERE 条件中必须包含索引的最左列(最左前缀原则),否则索引可能失效- 使用复合索引时,最左列必须在 WHERE 条件中出现,其余列可选,但参与条件过滤的列越多,查询效率越高
使用复合索引查询时,第一项不能为null示例(非海量数据时不好体现),这里主要展示使用复合索引查询需要包含索引第一项的含义,即where条件中需要包含索引第一项,下面以使用复合索引查询非复合索引中项(即需要回表)为例:

其中,第一个select语句表示使用复合主键的第一项进行查询,第二个select语句表示使用复合索引进行查询,但索引第一项为null,所以索引失效,查询效率低,第三个select语句表示使用复合索引进行查询,且所有项均不为空,所以索引有效,且查询效率最高
索引的创建原则:
比较频繁作为查询条件的字段应该创建索引- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
更新非常频繁的字段不适合作创建索引
引失效,查询效率低,第三个select语句表示使用复合索引进行查询,且所有项均不为空,所以索引有效,且查询效率最高
索引的创建原则:
比较频繁作为查询条件的字段应该创建索引- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
更新非常频繁的字段不适合作创建索引- 不会出现在where子句中的字段不该创建索引