MySQL索引

目录

一、引言:为什么需要索引

二、索引的基本概念

[2.1 什么是索引](#2.1 什么是索引)

[2.2 索引的分类](#2.2 索引的分类)

三、MySQL索引底层原理

四、EXPLAIN命令

五、SQL调优

[5.1 场景一:通过学号查询学生信息](#5.1 场景一:通过学号查询学生信息)

[5.2 场景二:通过姓名查找学生信息](#5.2 场景二:通过姓名查找学生信息)

[5.3 回表查询](#5.3 回表查询)

[5.4 索引覆盖](#5.4 索引覆盖)

[5.5 索引下推](#5.5 索引下推)

[5.6 索引的利弊](#5.6 索引的利弊)

六、索引失效

[6.1 不遵循最左匹配原则](#6.1 不遵循最左匹配原则)

[6.2 范围查询后的列无法使用索引](#6.2 范围查询后的列无法使用索引)

[6.3 where子句中有or,且有的条件列不是索引列](#6.3 where子句中有or,且有的条件列不是索引列)

[6.4 Like 查询以 % 开头](#6.4 Like 查询以 % 开头)

[6.5 隐式转换,比如字符串类型的字段没有加引号](#6.5 隐式转换,比如字符串类型的字段没有加引号)

[6.6 where子句中有表达式或函数](#6.6 where子句中有表达式或函数)

七、结语


一、引言:为什么需要索引

最近一次写博客可以追溯到十月份了,过去的这两个多月时间里,找实习、做项目......以至于好久没有认认真真的去夯实基础了,下面进入正题。

在inde_demo表中,除了给id设置自增主键索引外,其余的列都暂未创建索引,主要是为了对比有无索引的查询效率。与此同时,我还往表中插入了一百万条数据,便于做测试。

首先,解释一下上面执行的命令。

mysqlslap是MySQL官方自带的压测工具,用于模拟多客户端并发访问MySQL,测试数据库在指定场景下的性能。

--concurrency=100:指定并发客户端数为100。

--iterations=100:指定测试迭代数。

--number-of-queries=10000:每次迭代的总查询数,由上述指定的100个客户端分摊查询。

总的查询数 = 指定迭代数 * 每次迭代的总查询数。

--query="select ... where id = 1020000;":指定要执行的SQL语句。

接下来,分析两次执行结果。

第一、第二条命令(中间的不算),找的都是同一个人的行数据,但是耗时上存在很大差异。本质原因就在于是否使用了索引。所以,索引的存在就是为了加快查询效率。至于它如何做到的后面会说,不急。

中间的命令之所以被我强制停止执行,是因为查询次数是在比较多,同时有没有使用索引,到时我电脑的风扇在呼呼转,转了好一会也没得到结果,所以就把查询总数调小了不少再继续执行。

二、索引的基本概念

2.1 什么是索引

官方定义:索引(Index) 是数据库中一种特殊的数据结构 ,它通过预先对表中的一个或多个列的值进行排序和组织,以加快数据检索速度。通俗来讲,索引就好比一本书的目录,通过检索目录,我们可以快速定位到某个章节。类似地,在数据库中,我们可以通过索引快速找到我们想要的数据行。

2.2 索引的分类

在逻辑上,索引可以分为四类:

1)主键索引:主键值必须唯一且不能为NULL,每张表只能有一个。

2)唯一索引:对应的键值必须唯一,但是可以为NULL,一张表中可以有多个唯一键索引。

**3)普通索引:**对应的键值允许重复,并且可以为NULL,一张表中可以有多个。

4)全文索引:专门针对文本内容进行关键字搜索的索引。允许重复值和NULL,一张表中可以有多个。

在物理存储上,索引又可以分为两类:聚簇索引非聚簇索引

所谓聚簇索引,一句话:索引即数据,数据即索引。

非聚簇索引:索引是索引,数据是数据。

聚簇索引的叶子节点存储的是完整的行数据,而非聚簇索引的叶子节点只存储键值和数据行的物理地址(MyISAM)或主键值(InnoDB),不存储完整的行数据。

三、MySQL索引底层原理

MySQL索引的底层数据结构是B+Tree。B+树的数据和索引是分离的,所有数据只存储在叶子节点,所有叶子节点通过双向链表进行连接。

MySQL为什么选择B+树而不是B树?

B树和B+树的主要区别在于,B树的所有节点都存数据,而B+树只有叶子结点存储数据。假设要查询user_id = 24的用户,B树可能在第二层就找到了,但是B+树任何时候都要一直遍历到叶子结点才能找到数据。也就是说,在这当前场景下,B树的查询效率要比B+树的高。但是MySQL为什么不选择B树呢?其实,B+树每一次查询数据都要遍历到叶子结点,这不是缺点。正是因为它每一次都要到叶子结点中去拿数据,所以B+树的查询性能是稳定的,可预测的,而数据库要的就是稳定且可预测的查询性能。

更重要的原因在于,B+树的范围查询相对于B树来说,有着很大的优势。

我们看看B树的结构:

假设要查询age在10到30之间的用户,找到age = 10后,还需要回溯到父节点,产生多次上下遍历。导致很多随机的磁盘IO。而如果是B+树,在找到age = 10后,只需要沿着链表顺序往后遍历知道age > 30即可,完全是顺序IO。这样,B+树就充分利用了局部性原理,自然磁盘IO就要少一些。我们都知道,磁盘IO是很低效的,所以较少的磁盘IO自然效率就高。数据库的范围查询需求是比较多的,如果需要进行全表扫描,那么B+树的优势将会体现的淋漓尽致。

四、EXPLAIN命令

在执行SELECT、DELETE、INSERT等SQL语句之前,可以通过EXPLAIN来分析SQL语句的执行情况,以便于优化。

EXPLAIN的用法很简单,只需要在SQL语句前加上EXPLAIN即可。下面解释一下各个字段的含义。

1)id:查询标识符,如果查询包含子查询或UNION,会有多个id值,数字越大越先执行,相同id从上到下执行。

2)select_type:查询类型。SIMPLE--简答查询,不包含子查询;PRIMARY--最外层查询;SUBQUERY--子查询。上图中都有体现。

3)table:访问的表。

4)partitions:匹配的分区。

5)type:访问类型(很重要的字段)。访问类型从好到坏排序:

  • system:表中只有一行数据,而且使用的是MyISAM存储引擎。
  • const:查询条件为主键索引或唯一键索引与常量进行比较时。

这条查询语句的条件是id(主键)与常量进行比较,所以类型为const。

  • eq_ref:主键或唯一非空索引使用等号=进行比较时。
  • ref:SQL语句中用了普通索引,返回的结果可能是多行组成的结果集。

class_id相同的,也就是同一个班的,完全可能有多个。

  • ref_or_null:SQL语句中用了普通索引,索引列可以为空,并且包括对NULL值的检索。
  • range:使用索引列进行范围查询时。
  • index:扫描整个索引树时。
  • ALL:最差情况,表示需要进行全表查询才能找到指定数据行。

6)possible_keys:可能使用到的索引。

7)key:实际使用的索引。

8)key_len:索引的长度。

9)ref:与索引比较的列或常量。

10)rows:MySQL预估找到目标行需要检查多少行。对于优化很重要,值越小越好。

11)filtered:过滤百分比,表示存储引擎返回的数据在server层过滤后,剩余多少百分比。filtered * rows = 结果集行数。

12)Extra:额外信息。常见值含义如下:

  • Using index:使用索引覆盖。
  • Using where:使用非索引列进行检索数据。
  • Using temporary:使用了临时表。
  • Using filesort:使用文件排序。
  • Using index condition:表示索引下推。

五、SQL调优

5.1 场景一:通过学号查询学生信息

上述SQL语句很顺利的查询到了对应学生的信息。通过EXPLAIN进行分析,我们看到type为ALL,意味着全表扫描。并发量低的情况下,看不出明显的延迟,但是一旦并发量上去,这样的SQL语句效率是极低的,必须优化。

通过EXPLAIN可以看到,上述SQL语句没有使用索引,索引我们第一步优化可以给sn加上索引。考虑到学号sn是唯一的,所以加上唯一键索引很适合。

alter table index_demo add unique index un_idx_sn (sn);

可以看到,为sn添加索引之后,type从ALL优化到了const。目前这条SQL语句的查询效率已经很高了。

5.2 场景二:通过姓名查找学生信息

同样是全表扫描,必须优化。因为名字有可能是重复的,所以创建普通索引比较合适。

alter table index_demo add index idx_name (name);

为name字段添加索引后,type优化到了ref。但是,还可以再进一步优化------索引覆盖

为name、age、class_id三列创建复合索引。

alter table index_demo add index idx_name_age_classId (name, age, class_id);

5.3 回表查询

我们在创建索引时,MySQL会构建对应的索引树。举个例子,5.2节中我们为`name`字段创建了索引,那么MySQL就会构建出对应的索引树,这棵树的叶子结点存储的是索引的键值(name)和主键值(id)。

select name, age, class_id from index_demo where name = 'user_1020000';

我们通过上述的SQL语句查询信息时,就会用到`name`对应的索引,也就是说,先遍历`name`对应的索引树,因为该索引树中没有age和class_id,所以找到数据行对应的主键id后,还要返回到主表,遍历主表,通过id定位到数据行,才可以拿到age和clas_id这两个字段的信息。

通过主键id返回主表查询的操作,就是回表查询。在回表查询时,如果内存中没有缓存相应的数据页,那么将会涉及到磁盘IO,所以能不回表当然更好,下面要讲的索引覆盖就不需要回表。

5.4 索引覆盖

5.2节中,为name、age、class_id创建了复合索引,那么对应的索引树的叶子结点就会有主键id、name、age和class_id这些字段,包含了我们要查询的name、age和class_id字段信息,所以就不需要进行回表查询,这就是索引覆盖。

索引覆盖由于不需要进行回表查询,通常情况下效率是要更高的。

5.5 索引下推

索引下推(Index Condition Pushdown,ICP)是MySQL5.6以后引入的一项重要优化技术,它允许where子句中部分条件的判断从MySQL服务器层"下推"到存储引擎层执行。

下面举个例子,以 select * from users where age > 20 and name like '张%' and salary > 5000; 为例,user表中存在复合索引(age,name)。

传统查询(无ICP)流程:

1)存储引擎层:通过复合索引(age,name)找到age > 20的记录;

2)存储引擎层:将age > 20的记录(不管name)全部返回给服务器层;

3)服务器层:对返回的记录应用name like '张%' 和 salary > 5000过滤。

ICP查询流程:

1)存储引擎层:通过复合索引(age,name)找到age > 20的记录;

2)存储引擎层:在索引层面应用name like '张%' 进行过滤;------ 这个条件被"下推"到存储引擎层

3)存储引擎层:将同时满足age > 20和name like '张%' 的记录返回给服务器层;

4)服务器层:服务器层根据salary > 5000进行过滤。------ 不是索引列,无法"下推"。

Using index condition就说明发生了索引下推。

既然MySQL引入了索引下推,那么相对于没有索引下推,它必然存在一定的优势。主要体现在以下的两个方面:

  1. 减少回表次数

假设索引为 (city, age)

SELECT * FROM employees

WHERE city = '北京'

AND age > 30

AND department = '技术部';

无ICP:找到city = '北京'的记录(1000条)-> 全部回表(索引中没有department等字段信息) -> 服务器过滤。

有ICP:找到city = '北京'的记录(1000条)-> 在存储引擎层通过age > 30过滤(可能只剩200条)-> 回表(索引中没有department等字段信息)-> 服务器过滤。

  1. 减少数据传输

存储引擎层直接过滤掉不符合条件的记录,减少了存储引擎层和服务器层的数据传输量。

ICP的触发条件:

1)必须是二级索引(聚簇索引本身包含完整数据行,无需下推)。

2)where子句中至少有一个条件可以使用索引。

3)需要回表查询(索引覆盖无需回表,在检索索引树的时候,就已经在进行过滤了,也就无需下推)。

4)MySQL5.6+ 支持InnoDB或MyISAM。

5.6 索引的利弊

索引带来的好处很明显,就是可以加快我们的查询效率。

但是,我们知道,在创建索引时,MySQL是要创建出对应的索引树的,索引树的叶子节点上存储主键id和索引的键值,无疑是需要占用磁盘空间的。除此之外,我们在修改相关的字段时,索引树种的字段也要相应的修改,保证数据一致。所以,插入、删除、修改操作的效率就相对低一些。总之,合理的创建索引,带来插入、删除、修改操作效率的下降是可以接受的。

六、索引失效

6.1 不遵循最左匹配原则

最左匹配原则说的是,复合索引按照索引列的顺序从左到右匹配,且不能跳过中间的列,才能有效利用索引。

可以看到,where子句中的顺序和索引定义时的顺序是不一致的,但是仍然成功匹配了复合索引。原因在于,优化器帮我们调整了where子句中条件的顺序,本质上是遵循最左匹配原则的。

上面的两条SQL语句,第一条没有遵守最左匹配原则,所以并不能够使用索引。反观第二条,遵守了最左匹配原则,顺利的使用了索引来加快查询。

6.2 范围查询后的列无法使用索引

6.3 where子句中有or,且有的条件列不是索引列

gender列不是索引列。

6.4 Like 查询以 % 开头

name字段是索引列,但是因为以%作为模糊查询的开头,导致索引失效。

6.5 隐式转换,比如字符串类型的字段没有加引号

把sn当做了整形,而不是字符串,导致没有匹配上。

6.6 where子句中有表达式或函数

七、结语

终于写完了!如有错误,还请指出!


完~

相关推荐
DB虚空行者2 小时前
聊下几次线上删除MySQL导致的故障
数据库
骑着bug的coder2 小时前
第7讲:索引(下)——失效场景与优化实战
后端·mysql
一 乐2 小时前
健身房预约|基于springboot + vue健身房预约小程序系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习·小程序
代码游侠3 小时前
学习笔记——IO多路复用技术
linux·运维·数据库·笔记·网络协议·学习
云飞云共享云桌面3 小时前
河北某机器人工厂8个研发设计共享一台SolidWorks云主机
运维·服务器·网络·数据库·算法·性能优化·机器人
FixPng4 小时前
【数据库】MySQL基于MyCAT分库分表
数据库·mysql
哈里谢顿4 小时前
千万级订单表新增字段应该如何做?操作小结
mysql
虹科网络安全4 小时前
艾体宝洞察 | 生成式AI上线倒计时:Redis如何把“延迟”与“幻觉”挡在生产线之外?
数据库·人工智能·redis