MySQL索引详解
热衷学习,热衷生活!😄
沉淀、分享、成长,让自己和他人都能有所收获!😄
一、什么是索引?索引有什么作用?
索引是一种用于快速查询和检索数据的数据结构。常用的索引数据结构有:B树、B+树、Hash表。
索引的作用就相当于目录的作用。比如:我们在查字典的时候如果没有目录,我们就只能一页一页去查找字,速度很慢,如果有目录,我们只需要查找字所在的页数,然后直接翻到那一页就可以了。
索引的优点缺点
优点
- 索引可以让我们更快的检索出我们需要查找的数据,这也是创建索引的最主要的原因。
- 可以通过创建唯一索引,保证数据库行数据的唯一性。
缺点
- 创建索引和维护索引需要耗费更多的时间。当对表中的数据进行增删改时候,如果数据有索引,那么索引也需要动态的修改,会降低SQL执行效率。
- 索引需要使用物理文件储存,会消耗一定的空间。
但是,使用索引一定能提高查询性能吗?
大多数情况下,索引查询都是比全表扫描的速度要快的,但是如果数据库的数据量不大,那么使用索引不一定能带来很大的提升。
二、索引的底层数据结构
常用的索引底层数据结构有三种:B树、B+树、Hash表。
Hash表
哈希表就是键值对(key-value
)的集合,通过键(key
)可以快速查找到对应的值(value
),查询速度接近O(1)
。
哈希表通过哈希算法(也叫散列算法)计算key
对应的index
,然后通过index
就可以得到对应的value
。哈希算法有一个Hash冲突的问题,也就是多个key
最后得到的index
相同,通常使用链地址法结果Hash冲突的问题,就是将哈希冲突的数据存放在链表中。比如JDK1.8
之前你的HashMap
就是通过链地址法来解决哈希冲突,JDK1.8
以后HashMap
为了减少链表过长搜索时间过长引入了红黑树。
既然哈希表这么快,为什么MySQL没有使用其作为索引的数据结构呢?
主要有以下两个原因:
- Hash冲突问题:我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。
- Hash表不支持顺序查询和范围查询:这是Hash表最大的缺点,也是MySQL不将Hash表作为索引数据结构最重要的原因。
B树&B+树
B树也称B-树,全称为多路平衡查找树,B+树是B树的一种变体。
B树&B+树之间的差异
- B树的所有节点既存放键
key
也存放数据data
,而B+树只有叶子节点存放key
和data
,非叶子节点只存放key
。 - B树的叶子节点都是独立的,而B+树的叶子节点都有一条引用链指向它相近的节点。
- B树的检索过程是相当于对范围内的每个节点的关键词做二分查找法,可能还没到叶子节点检索就结束了,而B+树的检索效率就很稳定,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
为什么B+ 树比B 树更适合作为索引?
B+树的磁盘读写代价更低
B+树的
data
都存放在叶子节点,非叶子节点只存放key
,而B树的分支节点既存放data
也存放key
,这将导致B+树的层高会小于B树,所以B+树平均的IO次数会小于B树。B+树的查询效率更稳定
B树查询可能在非叶子节点就结束了,而B+树因为
data
只存放在叶子节点,所以查询必须从根节点到叶子节点,所以查询效率更稳定。B+树更便于遍历
由于B+树的
data
都存放在叶子节点,非叶子节点只存放key
,所以只需遍历叶子节点即可,而B树非叶子夜店也存放data
,要找到具体的数据需要进行二分查找。B+树更擅长范围查询
B+树叶子节点存放
data
,且data
是按顺序排列的双向链表,所以范围查询更快。而B树范围只能二分查找。B+树占用内存空间小
B+树非叶子节点不存放
data
比较小,在内存有限的情况下,相比于B树索引可以加载更多B+ 树索引。
三、索引类型
主键索引(Primary Key)
数据表的主键列使用的就是主键索引。
一张数据表有且只有一个主键,并且主键不能为null
,不能重复。
在MySQL
的InnoDB
的表中,如果表没有设置主键时,InnoDB
会自动先检查表中是否有唯一索引(Unique Key
)且不存在为null
的字段,如果有则选择该字段为默认的主键,如果没有InnoDB
会自动创建一个6Byte
的自增主键。
主键索引如下图:
二级索引(辅助索引)
二级索引又称为辅助索引,二级索引的叶子节点存储的数据是主键。当使用二级索引的时候,先通过二级索引查询到主键,然后通过主键获取数据。
当我们创建所以的时候需要选择索引类型,比如:唯一索引、普通索引、前缀索引、全文索引,这些都是属于二级索引。
- 唯一索引(
Unique Key
):唯一索引也是一种约束。唯一索引的属性列不允许出现重复的数据,但是允许数据为Null
,一张表允许创建多个唯一索引。使用唯一索引的目的大部分是为了保证该属性列的唯一性,而不是为了查询效率。 - 普通索引(Index):普通索引的唯一作用就是为了快速查找数据,一张表允许创建多个普通索引,并允许数据重复和为
null
。 - 前缀索引(Prefix):前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小。
- 全文索引(Full Text):全文索引主要是为了检索大文本数据中的关键字信息,是目前搜索引擎数据库使用的一种技术。
二级索引如下图:
四、聚簇索引和非聚簇索引
聚簇索引
聚簇索引是索引结构和数据存放在一起的索引,主键索引属于聚簇索引。
在MySQL
中,InnoDB
引擎的表的.idb
文件就包含了该表的索引和数据,该表的索引B+树
非叶子节点存放索引,叶子节点存放索引和索引对应的数据。
聚簇索引的优点
- 聚簇索引的查询速度非常快,因为
B+树
本就是一颗多平衡二叉树,叶子节点存放索引和数据,非叶子节点存放索引,且叶子节点也是有序的,定位到索引就可以得到数据。
聚簇索引的缺点
- 依赖有序的数据:因为
B+树
是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整数类型还好,否则类似字符串或者UUID
这种又长又难比较的数据,插入或者查找的速度肯定慢。 - 更新代价大:如果对索引列的数据修改时,那么对应的索引也需要修改,而且聚簇索引的叶子节点还存放这数据,修改代价很大,所以对于主键索引来说,主键一般是不可以被修改的。
非聚簇索引
上面说到的二级索引(辅助索引)都是非聚簇索引,非聚簇索引叶子节点只存储主键值。首先通过二级索引找到主键值,再通过主键索引找到数据。
非聚簇索引的优点
- 更新代价小:因为非聚簇索引的叶子节点值存放索引不存放数据,所以更新代价小。
非聚簇索引的缺点
- 跟聚簇索引一样也是依赖有序的数据。
- 可能会二次查询(回表):这应该是非聚簇索引最大的缺点,当通过索引查询到对应的主键时,可能还需要根据主键再到表中查询。
聚簇索引和非聚簇索引如下图:
非聚簇索引一定要回表查询吗(覆盖索引)?
举个栗子,用户准备使用SQL查询用户名,而用户名字刚好创建了索引,查询SQL如下:
1 | SELECT name FROM TABLE WHERE name = 'zhangsan'; |
那么这个索引的key本身就是name,查询对应的name直接返回就行了,无需返回表查询。
即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表, 因为它的主键索引的叶子节点存放的是指针。但是如果 SQL 查的就是主键呢?
1 | SELECT id FROM TABLE WHERE id = 1; |
主键索引本身的 key 就是主键,查到返回就行了。这种情况就称之为覆盖索引了。
五、覆盖索引
如果一个索引覆盖所有需要查询的字段,我们就称之为覆盖索引。在InnoDB
存储引擎中,如果不是主键索引,叶子节点存储的是主键,最终要是要回表,这样就会比较慢,覆盖索引就是可以把索引直接返回不需要做回表操作。
覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了, 而无需回表查询。
如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。
再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引, 那么直接根据这个索引就可以查到数据,也无需回表。
覆盖索引如下图:
六、联合索引
联合索引就是使用表中的多个字段创建的索引,也叫组合索引或者复合索引。
最左前缀匹配原则
最左前缀匹配原则指的是,在使用联合索引时,MySQL
会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询,如>
、<
、between
和已%开头的like查询
等条件才会停止匹配。
所以我们在使用联合索引时,可以将区分度高的字段放在最左边。
七、索引下推
索引下推是MySQL5.6
版本中提供的一项索引优化功能,可以在非聚簇索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表次数。
八、创建索引的注意事项
选择合适的字段创建索引:
- 不为
NULL
字段:索引字段的数据应该尽量不为NULL
,因为对于数据为NULL
的字段,数据库比较难优化。如果字段频繁被查询,但又避免不了为NULL
,建议使用0,1,ture,false
这样语义较为清晰的短值或者短字符作为替代。 - 被频繁查询的字段:我们创建索引的字段应该是查询操作非常频繁的字段。
- 被作为条件查询的字段:被作为
WHERE
条件查询的字段,应该被考虑建立索引。 - 频繁需要排序的字段:索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
- 被经常频繁用于连接的字段:经常用于连接的字段可能是一些外键列,对于外键并一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
- 不为
被频繁更新的字段应该慎重建议索引。
虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
尽可能的考虑建立联合索引而不是单列索引。
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗
B+树
。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,但修改索引时,耗费的时间也是很多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。注意避免冗余索引。
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如
name,city
和name
这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。考虑在字符串类型的字段上使用前缀索引代替普通索引。
前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引代替普通索引。
九、使用索引的一些建议
- 对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合建索引。
- 避免
where
子句中对字段施加函数,这会造成无法命中索引。 - 在使用
InnoDB
时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 - 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗
MySQL 5.7
可以通过查询sys
库的schema_unused_indexes
视图来查询哪些索引从未被使用。 - 在使用
limit offset
查询缓慢时,可以借助索引来提高性能。
十、MySQL 如何为表字段添加索引
添加
PRIMARY KEY
(主键索引)1
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` );
添加
UNIQUE
(唯一索引)1
ALTER TABLE `table_name` ADD UNIQUE ( `column` );
添加
INDEX
(普通索引)1
ALTER TABLE `table_name` ADD INDEX index_name ( `column` );
添加
FULLTEXT
(全文索引)1
ALTER TABLE `table_name` ADD FULLTEXT ( `column`);
添加多列索引
1
ALTER TABLE `table_name` ADD FULLTEXT ( `column`)