MySQL索引 总结

索引

什么是索引?

索引是一种可以快速查询数据的,有序的数据结构

索引的优点

  • 提升查询效率,减少IO次数

  • 在连表查询时,如果被驱动表的连接字段上建了索引,可以加快表连接的速度

    假设student表是驱动表,score表是被驱动表。查询过程大致是这样的:首先从student表中取出一条记录,然后拿着这条记录中的student_idscore表中查找匹配的记录。如果score表的student_id字段上有索引,那么查找匹配记录的过程就会很快,因为索引就像一本有序的目录,可以快速定位到相关的位置,而不需要逐条扫描整个score表。

  • group by和order by时,由于索引可以将数据进行排序了,因此减少分组和排序的时间

  • 通过创建唯一索引,可以保证每一行数据的唯一性

    唯一索引 是一种数据库约束,它要求索引列中的值不能重复。也就是说,对于唯一索引所涉及的列,数据库会自动检查插入或更新的数据是否违反了唯一性约束。

索引的缺点

  • 对数据进行增删改操作,索引也需要动态地维护,会降低SQL的运行效率
  • 索引需要占用一定的内存空间索引

索引结构选型

  • Hash索引:底层数据结构使用哈希表实现的,速度快,但只有精确匹配索引列的查询才有效,不支持顺序和范围查询,在发生哈希冲突时,通过链表来解决。但是innodb支持一种特殊的Hash,叫做自适应hash,结合了b+树和hash的特点

  • 二叉搜索树:二叉查找树查询的时间复杂度是O(log2N) 但是当二叉树不平衡,也就是数据是顺序插入的情况下,二叉树会退化成链表 查询效率会下降 变成O(n)。大数据量情况下,层级较深,检索速度慢

  • 红黑树: 红黑树是自平衡二叉树,只追求大致的平衡 并且插入和删除数据时间复杂度O(1) 因此应用广泛 如linux文件的索引、TreeMap的索引。但即使如此,由于红黑树也是一颗二叉树,所以也会存在一个缺点:大数据量情况下,层级较深,检索速度慢

  • B-Tree:B树称为多路平衡查找树 它是有序的,并且它的叶子节点和非叶子节点都可以存放索引和数据。

    B树的特点:5阶的B树,每一个节点最多存储4个key,对应5个指针

    ​ 一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂

    	     在B树中,非叶子节点和叶子节点都会存放数据
    
  • B+树:B树的变种,只有叶子节点才存放索引和数据,非叶子节点只存放索引。并且B+树同一高度的相邻节点用了双向链表来连接,因此进行范围查询时,只需要遍历链表就可以。而且查询次数很稳定,任何查询都是从根节点到叶子节点

  • 用B+树而不用B树或红黑树的原因:B+树只有叶子节点存数据,非叶子节点就可以存很多的索引值,从而增加了节点的分支因子,而B树或红黑树所有节点都可以存索引及数据,在相同的数据量下,B+树就会比较矮胖,IO次数少且稳定,而B树或红黑树会比较高瘦且IO次数不稳定;b+树的叶子节点可以用链表顺序遍历,实现范围查询,而b树或红黑树需要对整棵树进行中序遍历,范围查询的话 会带来比较多的IO

Innodb的B+树

B+树索引结构:

  • InnoDB使用B+树作为索引结构,这种树的每个节点都存储在数据库的页(Page)中,页是内存和磁盘交互的基本单位。
  • B+树的非叶子节点和叶子节点通过双向指针连接,形成一个链表结构,方便顺序访问。

非叶子节点:

  • 非叶子节点的页中包含目录项(Directory Entry)记录。
  • 每个目录项记录包含:
    • 索引值(用于确定数据的顺序)。
    • 指向下一条目录项记录的指针(在同一个页内)。
    • 指向下一层子节点页的地址(即子节点的页号)

叶子节点:

  • 叶子节点的页中包含用户记录(即实际的数据记录)。
  • 每个用户记录包含:
    • 索引值(与非叶子节点相同,用于确定数据的顺序)。
    • 指向下一条用户记录的指针(在同一个页内,形成链表)。
    • 行数据(实际存储的数据)

查询过程:

  • 假设我们要查询索引值为20的记录。
  • 第一步:从B+树的根节点开始,找到索引小于等于20且最靠近20的目录项记录。
  • 第二步:根据该目录项记录中指向下一层页的指针,找到下一层页的位置。这个过程需要一次IO操作(磁盘读取)。
  • 重复:在下一层页中,重复上述过程,找到索引小于等于20且最靠近20的目录项记录,并根据指针找到下一层页的位置。这个过程可能需要多次IO操作,具体次数取决于B+树的高度。
  • 到达叶子节点:最终,我们到达叶子节点的页。
  • 找到记录:在叶子节点的页中,找到索引值为20的用户记录。由于叶子节点的页是链表结构,我们可以通过指针顺序查找,直到找到目标记录。

聚簇索引和非聚簇索引

innodb的b+树的实现包括聚簇索引和非聚簇索引 而myisam的b+树实现只有非聚簇索引 并且myisam的非聚簇索引的叶子节点存储的不是具体的数据,而是数据文件的地址。

聚簇索引 即根据主键作为索引来排序的 叶子节点的用户记录包含了整条记录

非聚簇索引 即根据某个非主键字段作为索引来排序的 叶子节点的用户记录包含了这个字段的值还有对应的主键。因此如果以某个非主键字段作为索引去非聚簇索引里面查询,查询到对应的主键后,还需要拿着主键去聚簇索引里将其他的字段查出来,这个过程叫做回表

索引类型

  • 主键索引 数据库会自动创建主键索引 若没有设置主键 数据库会自动选择一个unique且not null的字段作为主键 若没有这样的字段 则隐式生成一个row_id作为主键
  • 普通索引 允许重复 允许null
  • 唯一索引 unique 可以为null
  • 联合索引 多个字段组成一个索引 专门用于组合条件的搜索
  • 覆盖索引 一个索引覆盖了需要查询的所有字段 这种情况下不需要回表

最左前缀匹配原则

索引的主要目的是要减少记录的扫描,也就是说在全表中,我们要过滤掉一些数据不进行扫描,这就是走索引的含义。

当然走索引还有另一个含义,就是显著提升了查询速度,一般是type达到了index以上,在真实项目环境中一般是这个含义。

举例子:有联合索引(a,b,c)

  • select * from table where a = 1 and b = 2 and c = 3 先根据a到索引树中查找a=1的记录,过滤掉大部分记录,由于a唯一,因此b有序,根据b在这些记录中查找b=2的记录,过滤掉部分记录,由于b唯一,因此c有序,根据c在这些记录中查找c=3的记录。因此a、b、c均走索引

  • select * from table where b = 3 and a = 2 and c = 9 MYSQL优化器优化了条件的顺序 同上 a、b、c均走索引

  • select * from table where a = 1 and b = 2 a、b走索引 c没用到

  • select * from table where a = 1 and b > 1 and c = 3 索引a查找a=1的记录,过滤掉大部分数据,由于a唯一,因此b有序,根据b在这些记录中查找b>1的记录,过滤掉部分记录,由于b不唯一且无边界,因此c=3这个条件在这部分数据里面,已经没有办法再过滤数据了。

  • 比如由a、b索引过滤后的数据如下

  • a=1 b=2 c=2

  • a=1 b=2 c=3

  • a=1 b=2 c=5

  • a=1 b=3 c=3

  • a=1 b=3 c=4

  • 由于c是无序的 因此没办法根据c=3的条件过滤掉一部分数据 只能全部扫描这些数据进行等值判断 因此a、b走索引,c不走索引。

    关键问题:一旦对 b 进行范围查询(b > 1),其后的索引列 c全局有序性被破坏。例如:

    • a=1, b=2 对应的 c 可能是 3, 4, 5(有序),
    • a=1, b=3 对应的 c 可能是 2, 3, 6(有序),
    • b=2b=3 对应的 c 值之间是无序的
  • select * from table where a = 1 and b >= 2 and c = 3 这条sql和上一条唯一区别就是b由无边界范围变成了有边界范围了 索引a和b过滤掉部分记录,虽然b不唯一,但是b有边界,在b的边界值上,c是有序的,也就是说当b=2时,c不等于3的数据可以直接过滤掉。

  • 比如由a、b索引过滤后的数据如下

  • a=1 b=2 c=2

  • a=1 b=2 c=3

  • a=1 b=2 c=5

  • a=1 b=3 c=3

  • a=1 b=3 c=4

  • 由于b=2时,c有序,因此c不等于3的数据可以直接过滤掉,而b>2的数据,依然要全部扫描,但是由于c索引过滤掉了部分数据,因此c算是走索引的了,只是效率依然低下

总结:
a唯一,b有序。
联合索引的最左匹配原则,在遇到范围查询(如 >、<)的时候,就会停止匹配,也就是范围查询的字段可以用到联合索引,但是在范围查询字段后面的字段无法用到联合索引。注意,对于 >=、<=、BETWEEN、like 匹配的范围查询,并不会停止匹配。

索引下推

索引下推(Index Condition PushDown,简称ICP)是从MySQL5.6开始引入的一个特性,索引下推通过减少回表的次数来提高数据库的查询效率

mysql的架构中,有连接层、server层和存储引擎层,server层主要是负责语法解析和优化,存储引擎层负责数据存储和读取。而索引下推的下推实际上就是将上层server层进行条件筛选的任务,交给了下层存储引擎层去做

在没有索引下推时,假设有一个联合索引(a,b),有一个sql语句的条件是是select a,b,c,d from table where a =1 and b != 2。首先会在存储引擎层根据索引a去索引树里找到a=1的数据,但是找到这些数据后,由于b索引失效(b没有明确的边界),因此会直接回表读取完整数据,根据主键查询到完整的a=1的数据,然后把这些数据交给server层,让server层来筛选b != 2的数据。所以说a>1的这些数据,每条数据都进行了一次回表.

mysql5.7后实现了索引下推,在存储引擎层,根据索引a去索引树找到a=1的数据,然后对于这些数据,在存储引擎层筛选b != 2的数据,最后再回表读取完整数据,这样就少掉了几次回表。

没有索引下推时:由于 b != 2 无法在索引中直接过滤,数据库需要回表读取完整的数据行。

有了索引下推:

  • 在存储引擎层,数据库直接利用索引中的 b 字段进行过滤,筛选出 b != 2 的记录。
  • 只有满足 a = 1b != 2 的记录才会被回表读取完整的数据行。

因此 索引下推面对索引失效的问题,可以有效减少回表。

官方给的例子是a=1 and b like %b,实际上也是索引失效问题

适合建索引的情况

  1. 数据量庞大
  2. 经常出现在where子句的字段
  3. 经常group by和order by的字段
  4. 多表连接 被驱动表的连接字段
  5. 需要去重的distinct字段

DISTINCT 关键字用于去除查询结果中的重复记录。当查询中包含 DISTINCT 时,MySQL需要检查每一行数据,以确保它是唯一的。如果没有适当的索引,数据库将不得不执行全表扫描,这会导致查询效率低下

不适合建索引的情况

  1. 数据量小,索引的维护成本可能高于查询优化带来的性能提升

  2. 不经常出现在where子句的字段

    增删改的事后,索引也要同时更新,耗时

  3. 经常增删改的字段

  4. 区分度低的字段 如sex

索引失效的情况

  1. 在联合索引中,如果某个字段在无边界的范围查询字段的右边,则该字段索引失效

  2. 左模糊或全模糊

    索引是基于列值的顺序建立的,而放在开头的通配符使得数据库无法有效利用索引来缩小搜索范围,从而导致全表扫描。例如,查询条件LIKE '%Smith'会使得MySQL进行全表扫描,因为索引无法匹配通配符在最左边的情况

  3. 在索引字段上进行计算或使用函数

  4. 使用 != 或者is not null 会导致全表扫描

    选择性低:如果使用 != 操作符的值在列中非常普遍,那么这个条件的选择性就很低,意味着这个条件不能有效地减少结果集的大小。在这种情况下,查询优化器可能会认为使用索引的成本高于全表扫描的成本,因此选择不使用索引。

    IS NOT NULL:如果列中的 NULL 值很少,IS NOT NULL 的选择性很高,索引仍然可能被使用。但如果列中大多数值都不是 NULL,这个条件的选择性就很低,索引可能不会被使用。

  5. 隐式转换 比如where的num=100 等号两边 一个是数字类型 一个是字符串类型

  6. or前后存在非索引列

  7. mysql优化器认为全表的速度大于走索引的速度

你可能感兴趣的:(MySQL索引 总结)