7.1 什么是索引
索引(index)是帮助MySQL高效获取数据的数据结构(有序)
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
索引的优劣分析:
优势:
- 提高数据检索的效率,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
劣势:
- 索引列也是要占用空间的
- 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行 INSERT、UPDATE、DELETE 时,效率降低
7.2 索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种数据结构:
索引结构 | 描述 |
B+Tree 索引 | 最常见的索引类型,大部分引擎都支持 B+ 树索引 |
Hash 索引 | 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-tree(空间索引) | 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式。类似于 Lucene,Solr,ES |
不同存储引擎对于索引结构支持情况:
索引 | InnoDB | MyISAM | Memory |
B+tree 索引 | 支持 | 支持 | 支持 |
Hash 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本之后支持 | 支持 | 不支持 |
我们平常所说的索引,如果没有特别指明,都是指 B+树 结构组织的索引
7.3 MySQL索引中的 B+Tree
首先介绍 多路平衡查找树----B-Tree
以一颗最大度数(树的度数指的是一个节点的子节点个数)为5的b-tree为例(每个节点最多存储4个Key,5个指针)
键和值绑定在一起放入节点中,通过键大小来查询值B+Tree
最底层才是一个排好序的存储单元,上面部分都只是用于存储键便于查询
以一颗最大度数为4的b+tree为例:
MySQL索引中的 B+Tree
MySQL索引数据结构对经典的 B+Tree 进行了优化,在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。
7.4 索引分类
分类 | 含义 | 特点 | 关键字 |
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键字,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
分类 | 含义 | 特点 |
聚集索引 | 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
二级索引 | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引 选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
- 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引
见图:
若根据条件建立的索引需要查询其他的值,则需要根据叶子节点关联的主键回到聚集索引查询出整行信息,这种方式就叫 回表查询
7.5 索引语法
- 创建索引
create [unique | fulltext] index 索引名 on 表名 (列名,...);
# 默认为常规索引
- 查看索引
show index from 表名;
- 删除索引
drop index 索引名 on 表名;
7.6 索引的失效情况
- 最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。指的是查询从索引的最左列开始,并且不跳过索引中的列。
如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
例如:创建的索引关联字段为 profession、age、statuscreate
index
idx_user_pro_age_staon
tb_user (profession,age,status);
- 分别进行不同条件的查询
第一次 对三个关联字段都进行查询,该索引长度为 54 - 第二次 查询,减少了字段 status,长度为 49,可以推出 status 所占索引长度为 5
第三次 查询,减少了字段 status 和 age,长度为 47,可以推出 age 所占索引长度为 2
所以 只含 profession 的索引长度为 47 - 开始验证 最左前缀法则,将最左边 profession 字段去掉进行查询:
- 可以看到查询时没有使用到索引,进行了全表扫描查询的数据,验证了 查询要从索引的最左列开始
再次进行验证,若含有最左边 profession 字段,但是缺失中间字段进行查询: - 可以看到使用了索引,但是索引长度为 47,说明只使用了只含 profession 的索引,验证了 如果跳跃某一列,后面的字段索引失效
再进行一个小细节的验证,关联查询字段都包含,但是将查询字段置换顺序,进行查询: - 可以看到最后索引长度为 54,说明该索引包含了 三个字段,这说明 where条件 查询时字段顺序与索引关联字段顺序无关,说白了就是,索引只在乎它按顺序关联的字段在不在,不在乎查询时的字段顺序
- 范围查询
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效
例如:(还是用上面的索引示范)
所以一般范围最好能加个 = ,这样后面的索引才不会失效 - 索引列运算
不要在索引列上进行运算操作,索引将失效
例如:给字段 phone 加了索引,但对该字段进行了运算操作
explain select * from tb_user where substring(phone,10,2) = '15';
- 该索引将会失效:
- 字符串不加引号
字符串类型字段使用时,不加引号,索引将失效 - 模糊查询
如果仅仅是尾部模糊查询,索引不会失效。如果是头部模糊匹配,索引失效。
例如:对 profession 进行模糊查询 - or 连接的条件
用 or 分割开的条件,如果 or 前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到
例如:条件字段中,id 有主键索引,phone 添加索引,age 未添加索引,进行查询
可以看到,由于 age 没有索引,所以即使id、phone有索引,索引也会失效。所以需要针对 age 也要建立索引 - 数据分布影响
如果MySQL评估使用索引比全表更慢,则不使用索引
例如:对 user_name 添加了索引,根据条件进行查询
可以看到,因为字段不为空的很多,所以MySQL选择了全表扫描,而不是走索引;第二次查询满足 null 的很少,所以走索引
7.7 索引的使用细节
- SQL提示
是优化数据库的一个重要手段,就是在SQL语句中加入一些人为的提示来达到优化操作的目地。 - 覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少 select
using index condition:查找使用了索引,但是需要回表查询数据
using where;using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
- 前缀索引
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
语法:
create index idx_xxxx on table_name(column(n));
前缀长度:
可以根据索引的 选择性 来决定,而选择性是指 不重复的索引值(基数)和 数据表的记录总数 的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
- 单列索引与联合索引
单列索引:即一个索引只包含单列
联合索引:即一个索引包含了多列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引
减少回表查询
7.8 索引设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率
- 如果索引列不能存储NULL值,请在创建表时使用 NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询