1.索引的代价(索引的缺点)
1.1.空间代价
每建立一个索引都要为它建立一棵B+树,每棵B+树的每个节点都是一个数据页,每个页大小是16KB,一棵很大的B+树由很多数据页组成,那将是很大的一片存储空间。
1.2.时间代价
每次对表中的数据进行增删改操作时都需要修改相应的B+树索引,而B+树每层的节点都是按照索引列的值从小到大排序形成的双向链表,节点内的记录也是按照索引列的值从小到大排序形成的单向链表,这些写操作势必会对节点和记录的顺序造成破坏,所以存储引擎需要额外的时间维护好它们的顺序。
2.MySQL索引的类型
Innodb存储引擎中只使用了B+树结构的索引,而Memory存储引擎中可以使用Hash结构的索引和B+树结构的索引。
MySQL的索引类型主要包括以下几种:
- 主键索引:为主键字段创建的索引,不能重复,不能为null
- 唯一索引:只要求列中的值不能重复,但可以有多个null,只是非null的值不能重复
- 普通索引:最基本的索引,可以包含重复值和null
- 联合索引:由多个字段组合而成的普通索引,也可同时指定为唯一索引
- 全文索引:本质是一个倒排索引(将文本拆分成单独的词条,每个词条映射到包含该词条的文档集合),可以快速查询在大量文本数据中包含某些词汇的记录,它特别适合在 CHAR, VARCHAR, 或 TEXT 类型的列上执行文本搜索操作。但是性能、效果都不如es
- 空间索引:基于R树的数据结构,可以看作是B树在多维空间的扩展。用来做地理空间数据的快速搜索
3.索引覆盖(为什么要解决回表?回表哪不好了?)
以idx_name_birthday_phone_number 索引为例,看下边这个查询:
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';
- 从索引idx_name_birthday_phone_number 对应的B+ 树中取出name 值在Asa ~ Barlow 之间的用户记录。
- 由于索引idx_name_birthday_phone_number 对应的B+ 树用户记录中只包含name 、birthday 、phone_number 、id 这4个字段,而查询列表是* ,意味着要查询表中所有字段,也就是还要包括country字段。这时需要把从上一步中获取到的每一条记录的id 字段都到聚簇索引对应的B+ 树中找到完整的用户记录,也就是我们通常所说的
**回表**,然后把完整的用户记录返回给查询用户。
解释顺序IO和随机IO需要用回表的两步来解释:
**顺序I/O**:由于索引idx_name_birthday_phone_number 对应的B+ 树中的记录首先会按照name 列的值进行排序,所以值在Asa ~ Barlow 之间的记录在磁盘中的存储是相连的,集中分布在一个或几个数据页中,我们可以很快的把这些连着的记录从磁盘中读出来,这种读取方式我们也可以称为顺序I/O。
**随机I/O**:根据第1步中获取到的记录的id 字段的值可能并不相连,而在聚簇索引中记录是根据id (也就是主键)的顺序排列的,所以根据这些并不连续的id值到聚簇索引中访问完整的用户记录可能分布在不同的数据中,这样读取完整的用户记录可能要访问更多的数据页,这种读取方式我们也可以称为随机I/O。
:::info 所以这个使用索引idx_name_birthday_phone_number 的查询有这么两个特点:
会使用到两个B+ 树索引,一个二级索引,一个聚簇索引。
访问二级索引使用顺序I/O ,访问聚簇索引使用随机I/O 。
:::
需要回表的记录越多,使用二级索引的性能就越低,甚至让某些查询宁愿使用全表扫描也不使用二级索引
3.1.怎么解决回表
回表是因为查询的列中数据不完整,还需要去聚簇索引中获取剩下的字段,所以如果查询的列中只包含索引列,那就不需要回表了。
如果一个索引中的列包含(覆盖)你要查询的所有列,那这就是**索引覆盖**
4.最左匹配原则
在联合索引中,搜索条件中必须出现从**<font style="color:#DF2A3F;">最左边连续的列</font>**才可以使用到这个索引,这就是**最左匹配原则**
因为联合索引的排序原理是:
先按照索引 第一列 的值进行排序。
如果 第一列 的值相同,则按照 第二列 的值进行排序。
如果 第二列 的值也相同,则按照 第三列 的值进行排序。
. . . . . .
单看任何一个非最左侧的列都是无序的
5.索引下推
**索引下推**即允许存储引擎在索引层面就对记录进行过滤,而不是将索引层面检出的数据上推到服务层再进行过滤,从而减少了传输到服务层的数据量。是在 MySQL 5.6 及以后版本的查询优化器的一个特性,用于提高查询性能。
适用场景:
- 联合索引中的非前缀列筛选:在使用包含多个列的联合索引时,如果查询条件中涉及到索引列的非最左前缀部分,ICP 可以在索引层进行筛选,而不是必须读取表中的完整数据行后才能进行筛选。
- 复杂的索引列条件:如果索引列参与了复杂的条件判断,如 LIKE 表达式、数学运算或函数,ICP 允许在检索索引条目时就应用这些条件,减少了不必要的数据行查阅。
- 部分匹配的范围查询:当进行范围查询且查询条件只部分匹配索引时,ICP 可以在索引层面减少被考虑的数据集大小。
6.稠密索引、稀疏索引
稠密索引 和 稀疏索引 强调的是索引项(目录项)的密度。反映了索引项与数据记录之间的对应关系密集程度的不同。
稠密索引:每一条记录都有对应的索引项。
稀疏索引:为每一个数据页建立一个索引项,因一般是为每个页中最小记录建立索引项。
7.索引使用的建议
7.1.哪些列有必要创建索引?哪些列没必要创建索引?
- 只为出现在WHERE 子句中的列、连接子句中的连接列,或者出现在ORDER BY 或GROUP BY 子句中的列创建索引
- 查询列表中的列不需要创建索引
7.2.考虑的列的基数,基数越大索引效果越好
列的基数指的是某一列中不重复数据的个数。
假设某个列的基数为1 ,也就是所有记录在该列中的值都一样,那为该列建立索引是没有用的,因为所有值都一样就无法排序,无法进行快速查找了~ 而且如果某个建立了二级索引的列的重复值特别多,那么使用这个二级索引查出的记录还可能要做回表操作,这样性能损耗就更大了。
结论就是:最好为那些列的基数大的列建立索引,为基数太小列的建立索引效果可能不好。
7.3.索引类的类型尽量小
以整数类型为例,有TINYINT 、SAMLLINT、MEDIUMINT 、INT 、BIGINT这么几种,它们占用的存储空间分别是1字节、2字节、3字节、4字节、8字节,我们这里所说的类型大小指的就是该类型表示的数据范围的大小。能表示的整数范围当然也是依次递增,如果我们想要对某个整数列建立索引的话,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如我们能使用INT 就不要使用BIGINT ,能使用MEDIUMINT 就不要使用INT。
原因是:数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,树的高度就越小,从而减少磁盘I/O 带来的性能损耗;另外查询时比较操作就越快。
8.索引失效的场景
1. 应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用 索引而进行全表扫描
2. 尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使 用索引而进行全表扫描
3. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎 放弃使用索引而进行全表扫描
4. in和not in也要慎用,否则会导致全表扫描
5. like 语句不允许使用 % 开头,否则索引会失效
6. 应尽量避免在where子句中对字段进行表达式操作,这样导致引擎放弃使用索引而进行全表扫描
7. 尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描