从头开始搞懂 MySQL(04)索引

1、常见索引

我们学习数据库时肯定会提到索引,或者在日常工作中,如果有一个慢 SQL,分析原因后,我们可能就会说给某个字段加个索引。索引到底是什么东西呢?

索引是为了提高数据查询的效率,就像书的目录一样,在不看目录的情况下,我们想要找到一本书里面的内容,可能需要从头开始翻很久才能找到,有了目录之后,我们才能轻易找到想要的内容

1.1 常见索引结构

  • 哈希

    哈希是一种以 (key - value)键值对存储的数据结构,哈希结构和实现请看这里:哈希表

    哈希表这种结构适用于等值查询的场景,可以直接通过 key 来查询,但哈希表不是有序的,所以如果使用哈希索引进行区间查询的话速度比较慢

  • 有序数组

    有序数组在等值查询和范围查询的时候都比较适用,有序数组查询效率较高,可以通过 二分查找 来快速找到,但是更新数据比较麻烦,所以有序数组索引适用于静态存储引擎,对于一些不会更改的历史数据可以考虑使用有序数组索引

  • 二叉树的特点是每个节点的左节点小于父节点,父节点小于右节点。树可以有多叉,多叉树是每个节点有多个子节点,子节点大小保证从左到右递增,但实际上的数据库存储并不使用二叉树,因为我们无法保证树的高度,树越高每次查询数据读磁盘次数就越多,我们不应该使用二叉树,而应该使用多叉树

在 MySQL 中,索引是在存储引擎层面实现的,所以没有统一的索引标准,不同存储引擎的索引的工作方式并不一致。InnoDB 使用较为广泛,所以我们还是用 InnoDB 举例,分析 InnoDB 引擎的索引

2、InnoDB 索引

在 InnoDB 引擎中,表都是根据主键顺序以索引的形式存放,这种存储方式的表称为索引组织表。InnoDB 使用了 B+ 树索引模型,所以数据也都是存储在 B+ 树中的

每个索引在 InnoDB 中对应一颗 B+ 树

以下例建表 SQL 为例:

 create table test (
   id int primary key,
   card int not null,
   name varchar(20),
   index (card)
 )engine=InnoDB;

表中有两个索引 id 和 card,表中有四行数据,值分别为(50, 12234)、(100, 13829)、(150, 14272)、(200, 52349)索引结构如下:

在这里插入图片描述

根据叶子节点的内容,索引类型分为主键索引非主键索引

主键索引的叶子结点存放的是整行数据,主键索引也被称之为聚簇索引(clustered index)

非主键索引叶子节点存放主键的值,非主键索引称为耳机索引(secondary index)

2.1主键索引和普通索引的查询有什么区别

  • 如果通过主键查询,name只需要搜索 ID 这颗 B+ 树
  • 如果通过普通索引查询,需要先搜索 card 这个索引树,然后得到 ID 的值,再到 ID 索引树搜索一次

先通过普通索引查询到主键索引,再用主键索引去主键索引树查询,这个过程称为回表,也就是说,基于非主键索引的查询需要多扫描一张索引树,所以我们在编写 SQL 中应该尽量使用主键来查询

3、索引维护

索引是需要维护的,在插入新的值的时候,以上图为例,如果插入新的 ID 值为 300,只需要在 row4 后面再加入一个新的记录

如果插入的值为 180,逻辑上需要挪动200,空出位置,更糟糕的是如果后面的数据页满了的话,按照 B+ 树的算法,会申请一个新的数据页,挪动部分数据过去,这样性能肯定会收到影响。而且页分裂也会影响到数据页的利用率。如果删除了数据,数据页也会做合并操作,可以视为分裂过程的逆向操作

3.1 什么情况下使用自增主键

一般情况下,一些建表规范里面要求建表语句里面一定要有自增主键

自增主键是指在自增列上定义的主键,建表语句中为:NOT NULL PRIMARY KEY AUTO_INCREMENT

有了自增主键后,插入新的记录的时候可以不指定主键的值,默认会对当前主键最大值加 1 作为新增记录的主键

如果使用了自增主键,每次插入对索引树来说都是追加操作,不会挪动其它记录,也不会触发叶子结点分裂

主键长度越小,普通索引的叶子结点就越小,普通索引所占的空间就越小,从性能和存储空间来说,自增主键更为合理

如果只有一个索引,我们就不用考虑其它索引叶子结点大小的问题,我们将这个索引设置为主键,直接使用主键查询。

3.2 删除&重建索引

  • 对于非主键索引,只需要删除并重建对应的非主键索引即可
  • 对于主键索引重建,先重建主键索引,然后把所有的非主键索引重新建一遍

4、覆盖索引

在表 test 中,执行下面的 SQL,需要执行几次树的搜索,会扫描多少行?

 SELECT * FROM test WHERE card BETWEEN 14272 AND 62305;

我们来看一下这条 SQL 的执行流程:

  • 在 card 索引树上找到 card = 14272 的记录,取到 ID = 150
  • 到主键 ID 索引树上找到 ID = 150 对应的 row3
  • 在 card 索引树上挨个找在范围内的值,找到对应 ID,然后去 找到对应的主键索引,获取对应的记录,直到 card 索引树上不满足条件结束循环

在这个过程中,有两条符合查询条件的记录,查询过程中读了 card 索引树的三条记录(第一次,第二次,第三次退出循环),回表了两次(第一次,第二次)。因为要查询的记录只在主键索引上有,所以必须要回表。

如果查询的 SQL 为

SELECT id FROM test WHERE card BETWEEN 14272 AND 62305;

的话,我们只查 ID 的值,ID 的值已经存储在 card 索引树上了,所以可以直接提供查询结果给我们,不用回表,索引 card 覆盖了我们的查询要求,我们称之为覆盖索引

覆盖索引可以减少回表,提高查询性能。我们可以在高频查询请求字段使用覆盖索引,减少语句的执行时间,但也要考虑索引字段的维护,是否真的需要建立冗余索引来支持覆盖索引

5、最左前缀原则

索引项是按照索引定义里面出现的字段顺序排序的,最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符,只要满足最左前缀,就可以利用索引来加速检索

5.1 联合索引字段顺序

因为索引支持最左前缀原则,假设有 (a,b)这个联合索引,我们一般就不用单独在 a 上面建立索引了,所以我们如果可以通过调整顺序,少建一个索引,这个顺序我们可以优先考虑使用

如果既有联合查询,也有基于 a 或者 b 各自的查询,查询条件里面只有 b 的 SQL,是无法使用(a,b)这个索引的,我们就需要维护另外一个索引 (b),这种情况下我们就需要考虑空间的问题了,比如 a 字段比 b 字段大,我们创建索引的话就可以选择创建 (a, b)的联合索引和(b)的字段索引

问题

主键索引可以有多个字段,下面有一张表:

CREATE TABLE test (
  `a` int(18) NOT NULL,
  `b` int(18) NOT NULL,
  `c` varchar(20) NOT NULL,
  `d` int(4) NOT NULL,
  PRIMARY KEY (`a`, `b`),
  KEY `idx_ca` (`c`, `a`),
  KEY `idx_cb` (`c`, `b`)
) ENGINE=InnoDB;

看了上面的我们都知道,主键已经包含了 a、b这两个字段,那么在 c 字段上建立一个索引,就已经包含了三个字段了,为什么要创建(c, a)和(c, b)这两个索引呢?

下面是两条 SQL,为了这两个查询,这两个索引都是必须的吗?为什么呢?

SELECT …… FROM test WHERE c = 3 ORDER BY a;
SELECT …… FROM test WHERE c = 3 ORDER BY b;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一起来搬砖呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值