简洁的概念
- 有序的数据结构(hash索引除外),因为有序所有能够实现快速查找
- 索引不仅用于查找还用于排序场景,后者是经常被忽略的
索引数据类型
B-tree 索引:这是 MySQL 中最常见的索引类型,适用于大多数数据类型,包括整数、字符、日期等。B-tree 索引能够快速定位到满足条件的数据行,因此被广泛用于普通索引、唯一索引、主键索引等。
哈希索引:哈希索引适用于精确查找,只能用于支持哈希运算的数据类型,如整数。它在某些情况下可以提供比 B-tree 索引更快的查找速度,但不支持范围查询和排序。
全文索引:全文索引适用于文本数据类型,如 VARCHAR 或 TEXT,用于实现全文搜索功能。它使用特殊的数据结构来支持文本内容的快速搜索,例如搜索关键字或短语。
空间索引:空间索引适用于空间数据类型,如 POINT、LINESTRING 等,用于支持空间数据的空间查询,如范围查询和最近邻查询。
前缀索引:前缀索引允许您为索引列的前缀创建索引,而不是整个列的值。这在处理长文本或者较大字段时可以节省索引的空间和提升检索效率。如email,email的格式决定了基于前缀就能够查询
索引类型
主键索引,索引值是真实数据,而非主键索引,索引值是主键值,用于回表
主键索引 (Primary Key Index):
如果未指定primary key,mysql也会创建一个虚拟列作为主键
- 类型说明: 主键索引是一种唯一性索引,用于唯一标识表中的每一行数据。每个表只能有一个主键索引,通常是在创建表时定义的主键列。
- 特点: 确保主键列的值不重复且不为空,可以加速数据的检索和表之间的关联操作。
- 应用场景: 适用于需要确保数据完整性和唯一性标识的列,如用户表中的用户ID。
唯一索引 (Unique Index):
- 类型说明: 唯一索引确保索引列的值是唯一的,但允许空值(除非在创建索引时指定了 NOT NULL 约束)。
- 特点: 类似于主键索引,但不要求索引列是表的唯一标识,可以用于加速数据的检索和保证某些字段的唯一性。
- 应用场景: 适用于需要保证字段唯一性但不一定作为主键的场景,如邮箱地址、身份证号等。
普通索引 (Normal Index):
- 类型说明: 普通索引是最基本的索引类型,没有唯一性要求,可以包含重复的值。
- 特点: 用于加速对数据的查找,适用于大多数数据类型和查询需求。
- 应用场景: 适用于通用的查询加速需求,如根据用户名或者产品编号进行快速检索。
全文索引 (Full-Text Index):
- 类型说明: 全文索引用于对文本数据类型进行全文搜索,支持自然语言搜索和模糊匹配。
- 特点: 可以处理大段文本内容的搜索需求,如文章内容、产品描述等。
- 应用场景: 适用于需要实现高级搜索功能的应用,如博客系统、电商平台的产品搜索等。
组合索引 (Composite Index):
- 高并发场景适合组合索引,组合索引条件多,查询越精准,需要处理的数据越少,性能越好,需要产品层面限制筛选条件
- 最左匹配原则,A字段相同时基于B字段做查询
- 类型说明: 组合索引将多个列组合起来创建一个索引,可以加快涉及组合列的查询速度。
- 特点: 在查询中涉及到组合索引的列时,可以提升查询效率。
- 应用场景: 适用于根据多个条件进行筛选或者排序的复杂查询,如根据用户ID和注册日期进行查询。
B+树
结构图
数据只存在于叶子结点
B+树的数据只存在于叶子节点,B树非叶子节点也存储了数据,B+树在B树的基础上优化了这点,是因为如果非叶子节点也存储数据,那么每一个节点所存储的索引数据就少了,就需要更多的页,树的深度就大性能就越慢
双向链表
看清楚了叶子节点之间是用双向链表实现的不是单向的,不懂为什么很多地方都是画的单向链表,单向链表没法支持小于的情况,而且这居然还是个环状的, 这个环状有什么用呢?这个目前还不清楚,也想不出来,循环数据? >=100 or <= 10
双向指针
非叶子节点索引值是有双指针,指针左边数据小与等于当前值,右侧大于等于当前值,这一规则也决定了块的分裂与合并,因为需要去维护这一特性
注意指针也是占空间的,在计算一个节点能存储多少数据时需要考虑指针的开销
块分裂与合并
一个磁盘块就是要满载之后才会开辟下一个节点,每一个节点里面的数据是有序的,在插入数据的时候,如果使用自增,那么就会往最后一个节点中插入,如果最后一个节点满了,会重新写入一个节点中,不会出现节点分裂的情况,什么时候出现节点分裂呢,比如现在要查到一个中间的节点,而这个节点已经满了,那么就会分裂,分裂之后还可能导致后面的块也分裂,然后不断地影响,这就是其慢的原因
节点的大小-16k

之前就听说过数据库有个页大小的概念,原来是存储器的逻辑块,注意是逻辑块不是物理块,主存和磁盘以页为单位进行数据交换,刚开始的时候还有点疑惑,CPU的缓存不是按照64个字节来读的吗,这差别大了去了,注意看清楚,是主存和磁盘的交换单位是页,不是CPU与主存,64个字节是针对CPU和主存来讲得。
那问题来了,明明是4k为什么mysql中的磁盘块(别当成是B+树磁盘块,这简直是就是强加概念)是16k呢?看上图的局部性原理的介绍,基于空间局部性原理,每次读的大小是页的倍数,目标是只有一个,但是基于空间局部性,所以会将周围的页也读进去,所以是16k。在最开始了解B+树的时候还有些疑惑数据全部读到内存中岂不是要内存泄露,其实不会,因为每次只会读一个磁盘块的数据,基于索引找到下一个磁盘块的位置,所以内存中任意一个时候都只会有一个磁盘块的内存消耗,毛毛雨啦。所以关键的优化点在于磁盘的IO次数上,这也是B+树对B树的优化,将数据全部放到了叶子结点上,这样每次读的key就越多,磁盘IO次数就越少。
适合/不适合创建索引的场景
- 索引虽然能够提升查询效率,但维护索引的结果本身就是一个耗性能的操作,所以不能随便创建索引
- 在数据迁移的时候一般会先删除索引,等输入插入完之后再减索引能够提升数据插入的效率,因为每插入一条都需要维护索引,插入完成后一次性维护索引性能损耗更低
适合
- 常用于查询的字段
- 常用于排序的字段
- 外键字段
- 分组字段(分组依赖于排序)
不适合
-
重复数据多的字段,如性别,只有男、女,那么就会存在大量相同性别的数据,区分度低
索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引1的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。
-
表数据少-没必要
回表
非主键索引:查询出来的数据并非行数据而是主键字段值,然后再从主键索引查一次,原因是为了避免数据冗余
索引覆盖不需要回表:如果索引中就已经存在了所有需要查询的字段就不需要回表了
存储引擎实现的:回表是在存储引擎中实现的而非server层
索引下推
引擎层过滤:下推的含义是本应在服务层中做的过滤下推至存储引擎层处理
针对索引的优化:索引下推是针对索引的优化,没有命中索引就不会有索引下推,因为全表扫描本身就是在存储引擎层过的过滤,所以没有索引下推优化一说。
主键索引能命中索引下推吗:如果命中了主键索引,那么只需要根据主键值做过滤就行了根本用不着其他字段做过滤,所以不需要索引下推的优化
单值索引:不会触发索引下推,因为如果命中了单值索引,本身就能够在引擎层就直接过滤出数据,假设不能过滤数据那就是将索引值全部遍历出来做过滤,那这样索引的有序性就没有了,所以索引下推并非对单值索引的
复合索引:索引下推最主要的场景就是复合索引了,而且是针对复合索引中部分字段失效的场景,比如现在有一个复合索引(a,b),假设a.b索引字段都有效,那么就会在引擎层直接基于a.b做筛选,这本身就是在引擎层实现的并不存在优化。如果是只命中了复合索引的前面一部分字段,比如只命中了这里的a,而查询条件中带有b,那么对于索引而言是无法基于a.b去做查询的只能就与a,但是由于存在b的条件所以可以在引擎层就过滤掉
优点:
-
引擎层做过滤性能更加,因为直接在引擎层做了过滤就减少了引擎层到server层的IO开销
-
其次是减少了回表,没有索引下推的情况下是先回表后做过滤的,所以能够减少回表,回表是引擎层的能力而非server层所以并非是先做二级索引的查询然后再在server中过滤再回表,之前的疑惑点就在于以为回表是在server层实现的
索引覆盖
-
避免回表:索引覆盖是指查找的字段在索引中就存在不需要回表再查
-
索引级别的全表扫表:一些场景中,比如
看似不会走索引,但可能会发生索引覆盖会直接从某个索引中去计算而不会全表扫描select count(*) from tb -
如果命中的是主键索引就不算是索引覆盖:因为全量数据本身就在主键索引中,全表扫描就是扫描主键索引的叶子节点
-
多索引都能覆盖的选择:在select count(
,那么计算数量,只需要将每个索引对应的id数量相加就行了,所以这个时候应该选择索引数据量最少的哪个,经过测试的确如此*`)中使用的索引是非主键索引,刚开始一直不明白,现在终于知道是为什么了,这是因为count(`*`)又不带条件,所以会使用索引覆盖,但是选择哪一个索引进行全遍历呢?当然是去重后数据越少越好,而id数据是最多的,所以不会现在它,而选择其他索引,比如`name_index

- 为什么不建议使用
:除了IO开销以外,其由于需要全量字段,就不会满足索引覆盖select *
多索引的选择
结合业务场景使用复合索引:索引的创建一定要基于实际的应用场景,如果索引创建得好一般是不会出现同时命中多个索引的情况,如果使用到多个字段应考虑使用复合索引
只能命中一个索引:同一个sql中不可能同时使用两个索引,不同的索引意味着不同的排序根本没法同时使用,如果从where条件上看会命中多个索引,执行引擎会根据执行计划选择效率更高的那个,比如使用rows,rows越小回表次数就越少性能自然越高
根据不重复值的数量选择:索引会维护一个不重复值数量,值越大说明不重复的值越多,选择能力更强,查询性能更好,会优先选择
复合索引
最左匹配原则
有效性:复合索引(A,B),如果where条件中没有使用A,那么该索引无效,相反如果用了A没有用B,还是有效的,如果A是范围查询,则B字段无效,因为B是在A值相同的情况下有效,范围查询A的值不同,所以B无序,无序则无效
规则对排序字段也有效:如果sql为,则b字段无效... where a = 'xxx' order by b`,对于组合索引(a,b)生效,如果sql为`... where a > xx order by b
排序
复合索引(A,B),order By A desc,B desc也是可以命中索引的,倒过来就行了,但是A desc,B asc这就不行了,必须保持一致
如:(1,1)(1,2)(1,3)(2,1)(2,2)(2,3)
A asc B asc -> (1,1)(1,2)(1,3)(2,1)(2,2)(2,3) 原有顺序
A desc B desc -> (2,3)(2,2)(2,1)(1,3)(1,2)(1,1) 将原来的顺序倒转过来
A asc B desc -> (1,3)(1,2)(1,1)(2,3),(2,2),(2,1) 重新排序了
like的最左匹配原则
这里的最左匹配原则同复合索引一样,原理也是一样的
索引失效场景
-
不符合复合索引最左匹配原则
-
like左模糊查询
-
函数,索引存的值是原始值,而这里的条件是经过函数转换的
-
隐式转换
这里name是字符串,但被用作int类型值的查询,由于int类型的排序与字符串的排序不一样,所以索引失效
-
is null and is not null
由于索引是没有null数据的,所以索引对is null或者is not null都是不生效的
-
大量回表
如果经过索引查询出来的数据量很大,比如占了整体数据的50%,比如性别字段,此时就会产生大量的回表,这种性能还不如直接全表扫描,这本身也是一种优化策略,前面集中索引失效的原因是无法使用索引,这个是可以使用索引,但性能不高
索引基数
基数越高性能越好:表示有多少个不同的值,在where条件中可能涉及多个索引,在索引选择中,基数是一个很重要的指标,基数越高性能越高(因为选择性越好)



