从头开始搞懂 MySQL(04)索引
Posted 一起来搬砖呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从头开始搞懂 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;
以上是关于从头开始搞懂 MySQL(04)索引的主要内容,如果未能解决你的问题,请参考以下文章