创建正确的MySQL数据库索引

前言

1.索引基本认知

2.索引的使用

3.索引的原理

3.1 MyISAM存储引擎索引

3.1.1 主键索引

3.1.2 辅助索引

3.2 InnoDB存储引擎索引

3.2.1 主键索引

3.2.3 辅助索引

3.2.3.1 唯一索引

3.2.3.2 单列索引

4.索引使用场景总结

5.索引失效分析

6.索引优化

6.1 看SQL执行计划

6.2 覆盖索引

6.3 索引下推

7.总结

8.参考文档


前言

1)由于我们工作中使用MySQL的时候,绝大多数场景下都是基于InnoDB存储引擎的,因此本文主要基于InnoDB存储引擎进行分析。

2)经常有文章说索引就像是一本书籍(如《Java从入门到精通》)的目录,当我们需要查找某个知识点(如集合),我们只需要遍历一遍目录,我们就可以找到知识点在书中的页码,然后直接定位知识点(如果没有目录,我们只能从第一页开始翻阅查找)。此话对索引的作用描述的非常形象和生动,但原理却不尽如此。

1.索引基本认知

1)什么是MySQL数据库索引?

MySQL数据库索引是一种帮助MySQL高效获取数据的数据结构;

2)MySQL数据库索引处于MySQL三层逻辑架构中的哪一层?

索引是在存储引擎层实现的,因此不同的存储引擎会有不同的索引,InnoDB只支持我们创建B+TREE索引,不支持我们创建HASH索引;

3)MySQL数据库索引具体存在于什么文件中?

我们数据库有表结构定义文件、数据文件、索引文件以及日志文件等,InnoDB存储引擎的表索引和表数据在一起(即同一个文件)。

2.索引的使用

1)创建表的时候创建索引

CREATE TABLE `student` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `student_no` varchar(20) NOT NULL COMMENT '学号',
  `mobile` char(11) NOT NULL COMMENT '手机号',
  `name` varchar(128) NOT NULL COMMENT '姓名',
  `age` int(3) NOT NULL COMMENT '年龄',
  `sex` char(1) NOT NULL COMMENT '性别',
  `extra` varchar(1024) DEFAULT NULL COMMENT '扩展信息',
  PRIMARY KEY (`id`),
  UNIQUE KEY `student_no_UNIQUE` (`student_no`),
  KEY `mobile_index` (`mobile`) USING BTREE,
  KEY `name_age_sex_index` (`name`,`age`,`sex`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学员表'

2)添加索引

ALTER TABLE student ADD  UNIQUE KEY student_no_UNIQUE(`student_no`) USING BTREE;
ALTER TABLE student ADD  INDEX mobile_INDEX(`mobile`) USING BTREE;
ALTER TABLE student ADD  INDEX name_age_sex_INDEX(`name` ASC,`age`  ASC,`sex`  ASC) USING BTREE;

关键字说明:

PRIMARY KEY 主键索引;

UNIQUE KEY 唯一索引;

INDEX 创建普通索引|辅助索引|二级索引。

特殊说明:

索引列排序只能是缺省是ASC;

索引类型只能是缺省是BTREE,不可以是HASH(虽然不会报错,但是数据库会转换为BTREE)。

3.索引的原理

创建索引的目的是提高数据检索效率,属于一种空间换取时间的行为,即通过某种数据结构建立检索条件与其对应数据的映射关系帮助我们可以快速定位数据,接下来将介绍其具体实现原理。

由于我们工作中经常使用的存储引擎是InnoDB,MyISAM是经常用来和InnoDB进行对比而被提到的存储引擎,所以本文除重点介绍基于InnoDB存储引擎的索引外,还简单介绍一下MyISAM存储引擎的索引,目的是为了我们更深入的理解InnoDB存储引擎的索引。

对于其他存储引擎的索引,由于这些存储引擎本身使用就甚少了,所以本文不作介绍。因为学了是真有可能用不上,工作到至今一直没有用过MySQL的非InnoDB存储引擎。

3.1 MyISAM存储引擎索引

简单介绍MyISAM存储引擎的主键索引以及辅助索引。

3.1.1 主键索引

 

创建正确的MySQL数据库索引_第1张图片

一颗由索引健(主键ID)构成的B+TREE,叶子节点储存索引健+数据行记录地址,地址指向该主键ID对应的数据记录,即其索引和数据不在同一文件。

3.1.2 辅助索引

创建正确的MySQL数据库索引_第2张图片

一颗由索引健(mobile_INDEX)构成的B+TREE,叶子节点储存索引健+数据行记录地址,地址指向该主键ID对应的数据记录,即其索引和数据不在同一文件。

3.2 InnoDB存储引擎索引

主要介绍InnoDB存储引擎常用索引的实现原理,同时分析不同场景/条件下存储引擎如何一步步检索出数据。

3.2.1 主键索引

主键索引PRIMARY KEY数据结构如下:

创建正确的MySQL数据库索引_第3张图片

一颗由索引健(主键ID)构成的B+TREE,其中叶子节点存储的是记录的完整数据(属于聚集(簇)索引)。

执行查询SQL:

SELECT * FROM student WHERE id = 1;

此SQL数据检索过程只需检索主键索引即可拿到记录的完整数据。

3.2.3 辅助索引

辅助索引也称普通索引/二级索引,主要包括唯一索引、单列索引以及多列索引。

3.2.3.1 唯一索引

唯一索引sudent_no_INDEX数据结构如下:

创建正确的MySQL数据库索引_第4张图片

一颗由索引健(student_no字段)构成的B+TREE,其中叶子节点存储的是索引健+记录主键ID。

执行查询SQL:

SELECT * FROM student WHERE student_no = '1001';

因此数据检索过程分为如下两步:

1)通过索引健定位其在叶节点的位置,获取ID;

2)通过主键ID走主键索引检索记录的完整数据(这一步专业名词叫回表);

3.2.3.2 单列索引

单列索引mobile_INDEX数据结构如下:

创建正确的MySQL数据库索引_第5张图片

一颗由索引健(name字段)构成的B+TREE,其中叶子节点存储的是索引健+记录主键ID。

执行查询SQL:

SELECT * FROM student WHERE mobile = '183';

因此数据检索过程分为如下两步:

1)通过索引健定位其在叶节点的位置,然后继续根据链表找下一个节点,直到遇到不满足条件的数据止;

2)对满足条件的每个节点进行回表;

3.2.3.3 多列索引

ALTER TABLE student ADD  INDEX name_age_sex_INDEX(`name`,`age`,`sex`) USING BTREE;

多列索引(name_age_sex_INDEX),也称组合索引、联合索引数据结构如下:

 

创建正确的MySQL数据库索引_第6张图片

一颗由索引健(name+age+sex组合而成的数据结构)构成的B+TREE,其中叶子节点存储的是索引健+主键ID。

执行查询SQL 1:

SELECT * FROM student WHERE name = 'Jack6' AND age = 21 AND sex = '男';

1)通过索引健定位查询的记录的主键ID(检索过程中,先比较name,而后age,最后sex);

2)通过主键ID回表;

执行查询SQL 2:

SELECT * FROM student WHERE name LIKE 'Jack%' AND age = 20 AND sex = '男' AND extra is NULL;

由于InnoDB存储引擎组合索引遵循最左原则以及遇到范围查找后续索引列失效原则;在条件name LIKE 'Jack%' AND age = 20 AND sex = '男' AND extra is NULL中,name LIKE 'Jack%'属于范围查找,所以age = 20 AND sex = '男'索引列将会失效。数据检索过程分析:

创建正确的MySQL数据库索引_第7张图片

1)InnoDB通过name LIKE 'Jack%'检索name_age_sex_INDEX索引树;

2)找到叶子节点所在数据页;

3)把数据页拿到InnoDB内存中;

4)数据页内各个节点通过单向链表连接,从第一个节点开始检索满足name LIKE 'Jack%'的节点1,然后判断节点1中的age和sex列是否满足age = 20 AND sex = '男',如果满足则拿到节点1的id进行回表查询该id值对应完整记录,并放入结果集ret;如果不满足进入步骤5);

5)从叶节点1开始继续找,重复步骤4),直到遇到第一个不满足name LIKE 'Jack%'的页节点止;

6)把结果集ret返回给Server层;

7)然后Server层进行继续过滤其他非索引列查询条件,如extra is null;

8)最后把Server层筛选完成的数据返回给Client。

注:InnoDB存储引擎默认一页数据大小为16KB,如果一个节点按照16个字节算,一页可以存1024个节点,高度为3层的索引树就可以存放亿级别记录数,所以我们InnoDB索引树具有矮胖的特点,进而帮助我们高效获取数据。

4.索引使用场景总结

1)主键索引是创建表主键字段时数据库自动创建该索引;

2)表中频繁作为查询条件的列应该创建索引;

3)多表关联查询的时候,应该对关联字段建立索引;

4)排序列应该创建索引;

5)在查询列比较少的情况下,可以考虑对查询列建立组合索引;

6)统计或者分组列,可以考虑建立索引。

5.索引失效分析

1)在组合索引中,要求查询条件满足最左前缀原则;

2)在组合索引中,范围查找列后面的索引列均失效;

3)不要在索引上做计算;

4)索引字段上不要使用不等;

5)索引字段使用like不以通配符开头;

6)索引字段不要使用or。

6.索引优化

6.1 看SQL执行计划

我们可以通过数据库关键字EXPLAIN查看SQL执行计划:

执行计划

其中访问类型type代表查询性能的高低,key代表查询用到的具体索引,Extra查询额外信息。

1)访问类型type属性

type属性代表该SQL执行时的性能高低,我们可以通过该属性判断SQL执行是否符合预期,性能由高到低依次为:

类型

含义

举例

const

使用唯一索引或者主键,返回记录一定是1行记录的等值where条件

SELECT * FROM student WHERE id = 1;

eq_ref

连接字段主键或者唯一性索引,常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果

SELECT * FROM student_class t1 LEFT JOIN student t2 ON t1.sid = t2.id;

ref

针对非唯一性索引,使用等值(=)查询非主键,或者是使用了最左前缀规则索引的查询。

SELECT * FROM student WHERE name = 'Jack';

range

索引范围扫描,常见于使用>,<,between and ,in ,like等运算符的查询中

SELECT * FROM student WHERE name LIKE 'Jack%';

index

检索字段是索引列的子集且需要索引全表扫描

SELECT name FROM student ORDER BY name;

ALL

全表扫描

SELECT * FROM student;

这里主要介绍如何使用type属性的判定SQL性能高低,除以上类型外还有fulltext、ref_or_null、unique_subquery、index_subquery、index_merge等不常见类型不作介绍,具体可GOOGLE。

2)key属性

实际使用的索引,如果为 NULL ,则没有使用索引。

3)Extra查询额外信息

告诉我们该SQL查询条件在哪些地方发生过条件过滤(引擎层/Server层)。

类型

含义

举例

Using filesort

MySQL 对数据使用一个外部的文件内容进行了排序,而不是按照表内的索引进行排序读取,常见于order by和group by语句中

SELECT * FROM student ORDER BY age;

Using index

表示 SQL 操作中使用了覆盖索引(Covering Index),避免了回表,效率高

SELECT name, age, sex FROM student where name LIKE 'Jack%';

Using index condition

表示 SQL 操作中使用了索引下推(ICP),在引擎层进行了条件过滤

SELECT * FROM student where name LIKE 'Jack%' AND age = 18;

Using where

表示会在server层进行条件过滤

SELECT * FROM student where name LIKE 'Jack%' AND extra is null;

6.2 覆盖索引

查询列属于索引健的子集,在name_age_sex_INDEX索引:

创建正确的MySQL数据库索引_第8张图片

执行SQL:

SELECT name,age,sex FROM student WHERE name LIKE 'Jack%' AND age = 20 AND sex = '男' ;

查询过程中只需要检索索引树即可得到查询列,无需回表查询完整记录。

6.3 索引下推

在name_age_sex_INDEX索引:

创建正确的MySQL数据库索引_第9张图片

执行SQL:

SELECT name,age,sex FROM student WHERE name LIKE 'Jack%' AND age = 20 AND sex = '男' AND extra is NULL;

按道理生效的索引列只有name,InnoDB存储引擎应该检索满足name LIKE 'Jack%'条件的所有数据,然后将数据返回Server层进行age = 20 AND sex = '男' AND extra is NULL过滤,最后把过滤后的数据返回给Client。在MySQL5.6.x以前是这么做的,但是在MySQL5.7.x以后age = 20 AND sex = '男'查询条件会下推到存储引擎层过滤,不满足条件的数据直接不读取,这样就大大减少了存储引擎扫描的记录数量 ,这个过程叫索引下推)。

7.总结

创建正确的MySQL数据库索引_第10张图片

本文主要讨论如何创建正确的索引?

首选分析索引的原理、数据检索过程解析;其次总结索引使用、失效场景以及使用技巧;

然后通过SQL执行计划去检测我们创建的索引是否符合高效,最后创建正确的索引。

8.参考文档

https://dev.mysql.com/doc/refman/5.7/en/innodb-introduction.html

https://dev.mysql.com/doc/refman/5.7/en/create-index.html

《MySQL内核:InnoDB存储引擎》

《MySQL技术内幕:InnoDB存储引擎》
 

 

未经博主同意不得转载!

你可能感兴趣的:(数据库)