MySQL之InnoDB索引使用规则

MySQL之索引使用规则

注:本文基于Linux系统上MySQL v8.0.26进行讲解

1.验证索引使用效率

在讲解索引的使用原则之前,先通过一个简单的案例,来验证一下索引,看看是否能够通过索引来提升数据查询性能。在演示的时候,我们还是使用之前准备的一张表 tb_sku , 在这张表中准备了1000w的记录。

在这里插入图片描述

这张表中id为主键,有主键索引,而其他字段是没有建立索引的。 我们先来查询其中的一条记录,看看里面的字段情况,执行如下SQL:
select * from tb_sku where id = 1\G;
可以看到即使有1000w的数据,根据id进行数据查询,性能依然很快,因为主键id是有索引的。

在这里插入图片描述

那么接下来,我们再来根据 sn 字段进行查询,执行执行如下SQL:
SELECT * FROM tb_sku WHERE sn = ‘100000003145001’;
我们可以看到根据sn字段进行查询,查询返回了一条数据,结果耗时 20.78sec,就是因为sn没有索引,而造成查询效率很低。

在这里插入图片描述

那么我们可以针对于sn字段,建立一个索引,建立了索引之后,我们再次根据sn进行查询,再来看一
下查询耗时情况。

创建索引:
create index idx_sku_sn on tb_sku(sn) ;
然后再次执行相同的SQL语句,再次查看SQL的耗时。
SELECT * FROM tb_sku WHERE sn = ‘100000003145001’;
我们明显会看到,sn字段建立了索引之后,查询性能大大提升。建立索引前后,查询耗时都不是一个数量级的。

在这里插入图片描述
在这里插入图片描述

2.最左前缀法则

如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。

最左前缀原则就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。

3.范围查询

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。
在业务允许的情况下,尽可能的使用类似于 >= 或 <= 这类的范围查询,而避免使用 > 或 <

4.索引列运算

不要在索引列上进行运算操作, 索引将失效。

5.字符串不加引号

字符串类型字段使用时,不加引号,索引将失效。

6.模糊查询

如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。

7.or连接条件

用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。

8.数据分布影响

如果MySQL评估使用索引比全表更慢,则不使用索引。

9.is null 、is not null

is null 、is not null是否走索引,得具体情况具体分析,并不是固定的。

10.演示(最左前缀法则)

以 tb_user 表为例,我们先来查看一下之前 tb_user 表所创建的索引。
在 tb_user 表中,有一个联合索引,这个联合索引涉及到三个字段,顺序分别为:profession,age,status。
对于最左前缀法则指的是,查询时,最左变的列,也就是profession必须存在,否则索引全部失效。
而且中间不能跳过某一列,否则该列后面的字段索引将失效。

在这里插入图片描述

接下来,我们来演示几组案例,看一下具体的执行计划:
以下的这三组测试中,索引都会生效,只不过索引的长度不同。 而且由以下三组测试,我们也可以推测出profession字段索引长度为47、age字段索引长度为2、status字段索引长度为5。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

而通过下面的这两组测试,我们也可以看到索引并未生效,原因是因为不满足最左前缀法则,联合索引最左边的列profession不存在。

在这里插入图片描述

在这里插入图片描述

下述的SQL查询时,存在profession字段,最左边的列是存在的,索引满足最左前缀法则的基本条件。但是查询时,跳过了age这个列,所以后面的列索引是不会使用的,也就是索引部分生效,所以索引的长度就是47。

在这里插入图片描述

当执行SQL语句: explain select * from tb_user where age = 31 and status = ‘0’ and profession = ‘软件工程’; 时,是否满足最左前缀法则,走不走上述的联合索引,索引长度?
可以看到,是完全满足最左前缀法则的,索引长度54,联合索引是生效的。
注意 : 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关

在这里插入图片描述

11.演示(范围查询)

当范围查询使用> 或 < 时,走联合索引了,但是索引的长度为49,就说明范围查询右边的status字段是没有走索引的。

在这里插入图片描述

当范围查询使用>= 或 <= 时,走联合索引了,但是索引的长度为54,就说明所有的字段都是走索引的。

在这里插入图片描述

12.演示(索引列运算)

在tb_user表中,除了前面介绍的联合索引之外,还有一个索引,是phone字段的单列索引。

在这里插入图片描述

当根据phone字段进行等值匹配查询时, 索引生效。

在这里插入图片描述

现在我们先想要查询手机号后两位是15的用户,使用字符串截取函数,当根据phone字段进行函数运算操作之后,索引失效

在这里插入图片描述

注:虽然索引失效,但是还是可以查询的

在这里插入图片描述

13.演示(字符串不加引号)

接下来,我们通过两组示例,来看看对于字符串类型的字段,加单引号与不加单引号的区别:

经过下面两组示例,我们会明显的发现,如果字符串不加单引号,对于查询结果,没什么影响,但是数据库存在隐式类型转换,索引将失效。

在这里插入图片描述
在这里插入图片描述

14.演示(模糊查询)

接下来,我们来看一下这三条SQL语句的执行效果,查看一下其执行计划:
由于下面查询语句中,都是根据profession字段查询,符合最左前缀法则,联合索引是可以生效的,我们主要看一下,模糊查询时,%加在关键字之前,和加在关键字之后的影响
经过上述的测试,我们发现,在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字前面加了%,索引将会失效。

在这里插入图片描述
在这里插入图片描述

15.演示(or连接条件)

由于age没有索引,所以即使id、phone有索引,索引也会失效。所以需要针对于age也要建立索引。
注:age仍然还是在联合索引里的

在这里插入图片描述

然后,我们可以对age字段建立索引。
建立了索引之后,我们再次执行上述的SQL语句,看看前后执行计划的变化。
最终,我们发现,当or连接的条件,左右两侧字段都有索引时,索引才会生效。

在这里插入图片描述
在这里插入图片描述

16.演示(数据分布影响)

经过测试我们发现,相同的SQL语句,只是传入的字段值不同,最终的执行计划也完全不一样,这是为什么呢?
就是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。

在这里插入图片描述

17.演示( is null、is not null )

我们再来看看 is null 与 is not null 操作是否走索引。
执行如下两条语句 :

在这里插入图片描述

接下来,我们做一个操作将profession字段值全部更新为null。

在这里插入图片描述

然后,再次执行上述的两条SQL,查看SQL语句的执行计划。
最终我们看到,一模一样的SQL语句,先后执行了两次,结果查询计划是不一样的,为什么会出现这种现象,这是和数据库的数据分布有关系。查询时MySQL会评估,走索引快,还是全表扫描快,如果全表扫描更快,则放弃索引走全表扫描。 因此,is null 、is not null是否走索引,得具体情况具体分析,并不是固定的。

在这里插入图片描述

18.SQL提示

我们能不能在查询的时候,自己来指定使用哪个索引呢? 答案是肯定的,此时就可以借助于MySQL的SQL提示来完成。 接下来,介绍一下SQL提示。
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
1)use index : 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估)。
2)ignore index : 忽略指定的索引。
3)force index : 强制使用索引。

19.演示(SQL提示)

目前tb_user表的数据情况及索引情况如下:

在这里插入图片描述
在这里插入图片描述

把 idx_user_age, idx_email 这两个之使用过的索引直接删除
drop index idx_user_age on tb_user;
drop index idx_email on tb_user;

以下查询走了联合索引。

在这里插入图片描述

执行SQL,创建profession的单列索引:
create index idx_user_pro on tb_user(profession)
创建单列索引后,再次执行A中的SQL语句,查看执行计划,看看到底走哪个索引。
测试结果,我们可以看到,possible_keys中 idx_user_pro_age_sta,idx_user_pro 这两个
索引都可能用到,最终MySQL选择了idx_user_pro_age_sta索引。这是MySQL自动选择的结果。

在这里插入图片描述
在这里插入图片描述

以下进行sql人为优化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

20.覆盖索引

尽量使用覆盖索引,减少select *。 那么什么是覆盖索引呢? 覆盖索引是指 查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到 。

在这里插入图片描述

21.演示(覆盖索引)

接下来,我们来看一组SQL的执行计划,看看执行计划的差别,然后再来具体做一个解析。
explain select id, profession from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’ ;
explain select id,profession,age, status from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’ ;
explain select id,profession,age, status, name from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’ ;
explain select * from tb_user where profession = ‘软件工程’ and age = 31 and status = ‘0’;
上述这几条SQL的执行结果为如下:
从上述的执行计划我们可以看到,这四条SQL语句的执行计划前面所有的指标都是一样的,看不出来差异。
但是此时,我们主要关注的是后面的Extra,前面两天SQL的结果为 Using where; Using
Index ; 而后面两条SQL的结果为: Using index condition 。
因为,在tb_user表中有一个联合索引 idx_user_pro_age_sta,该索引关联了三个字段profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)。

在这里插入图片描述

为了大家更清楚的理解,什么是覆盖索引,什么是回表查询,我们一起再来看下面的这组SQL的执行过程。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思考题:
一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username =‘itcast’;
答案: 针对于 username, password建立联合索引, sql为: create index idx_user_name_pass on tb_user(username,password); 这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。

22.前缀索引

当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,因为可能我们索引的字段非常长,这既占内存空间,也不利于维护,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。所以我们就想,如果只把很长字段的前面的公共部分作为一个索引,就会产生超级加倍的效果,此时可以只将字符串的一部分前缀,指定索引列的长度,建立索引,这样可以大大节约索引空间,从而提高索引效率。
但是,我们需要注意,order by不支持前缀索引 ;数值类型不能指定前缀索引

前缀长度:
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,
索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

流程是:
先计算完整列的选择性 :select count(distinct col_1)/count(1) from table_1
再计算不同前缀长度的选择性 :select count(distinct left(col_1,4))/count(1) from table_1
找到最优长度之后,创建前缀索引 :create index idx_front on table_1 (col_1(4))

语法:

--n代表要提取字符串的前几个字符
 create index idx_xxxx on table_name(column(n)) ;

23.前缀索引(演示)

通过以下的计算完整列的选择性,我们发现截取前5个与截取前9个效果是一样的,从体积方面看所以我们选择前5个;
我们发现截取前4个的话选择性更低了

select count(distinct email) / count(*) from tb_user ;--1
select count(distinct substring(email,1,10)) / count(*) from tb_user ;--0.9583
select count(distinct substring(email,1,9)) / count(*) from tb_user ;--0.9583
select count(distinct substring(email,1,8)) / count(*) from tb_user ;--0.9583
select count(distinct substring(email,1,6)) / count(*) from tb_user ;--0.9583
select count(distinct substring(email,1,5)) / count(*) from tb_user ;--0.9583
select count(distinct substring(email,1,4)) / count(*) from tb_user ;--0.9167

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

create index idx_email_5 on tb_user(email(5));
show index from tb_user;

在这里插入图片描述

前缀索引的查询流程:
我们在辅助索引中查询到lvbu6后,到聚集索引里把数据拿出来,然后再进行接着在链表中查询lvbu6的下一个索引看看是不是也是lvbu6.如果是的话继续到主键索引里拿数据,如果不是的话,就不再查找,原因是我们的选择性不是==1

在这里插入图片描述

23.单列索引与联合索引

单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。

MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
联合索引,在建立索引的时候,尽量在多个单列索引上判断下是否可以使用联合索引。
联合索引的使用不仅可以节省空间,还可以更容易的使用到索引覆盖。试想一下,索引的字段越多,是不是更容易满足查询需要返回的数据呢。比如联合索引(a_b_c),是不是等于有了索引:a,a_b,a_b_c三个索引,这样是不是节省了空间,当然节省的空间并不是三倍于(a,a_b,a_b_c)三个索引,因为索引树的数据没变,但是索引data字段的数据确实真实的节省了。

联合索引的创建原则:
在创建联合索引的时候因该把频繁使用的列、区分度高的列放在前面,频繁使用代表索引利用率高,区分度高代表筛选粒度大,这些都是在索引创建的需要考虑到的优化场景,也可以在常需要作为查询返回的字段上增加到联合索引中,如果在联合索引上增加一个字段而使用到了覆盖索引,那我建议这种情况下使用联合索引。

联合索引的使用:
考虑当前是否已经存在多个可以合并的单列索引,如果有,那么将当前多个单列索引创建为一个联合索引。
当前索引存在频繁使用作为返回字段的列,这个时候就可以考虑当前列是否可以加入到当前已经存在索引上,使其查询语句可以使用到覆盖索引。

24.演示(单列索引与联合索引)

我们先来看看 tb_user 表中目前的索引情况
在查询出来的索引中,既有单列索引,又有联合索引。

在这里插入图片描述

接下来,我们来执行一条SQL语句,看看其执行计划:
通过下述执行计划我们可以看出来,在and连接的两个字段 phone、name上都是有单列索引的,但是最终mysql只会选择一个索引,也就是说,只能走一个字段的索引,此时是会回表查询的。

在这里插入图片描述

紧接着,我们再来创建一个phone和name字段的联合索引来查询一下执行计划
此时,查询时,就走了联合索引,而在联合索引中包含 phone、name的信息,在叶子节点下挂的是对应的主键id,所以查询是无需回表查询的。

在这里插入图片描述

在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
如果查询使用的是联合索引,具体的结构示意图如下:

在这里插入图片描述

25.索引设计原则

1)针对于数据量较大,且查询比较频繁的表建立索引。
2)针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
3)尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
4)如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
5)尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
6)要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
7)如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

26.避免回表

可以利用覆盖索引进行sql优化,比如以下:
这里就是一个典型的使用覆盖索引的优化策略减少回表的情况。

在InnoDB的存储引擎中,使用辅助索引查询的时候,因为辅助索引叶子节点保存的数据不是当前记录的数据而是当前记录的主键索引,索引如果需要获取当前记录完整数据就必然需要根据主键值从主键索引继续查询。这个过程我们成位回表。想想回表必然是会消耗性能影响性能。那如何避免呢?
使用索引覆盖,举个例子:现有User表(id(PK),name(key),sex,address,hobby…)
如果在一个场景下,select id,name,sex from user where name =‘zhangsan’;这个语句在业务上频繁使用到,而user表的其他字段使用频率远低于它,在这种情况下,如果我们在建立 name 字段的索引的时候,不是使用单一索引,而是使用联合索引(name,sex)这样的话再执行这个查询语句是不是根据辅助索引查询到的结果就可以获取当前语句的完整数据。这样就可以有效地避免了回表再获取sex的数据。

27.分析官方建议使用自增长主键作为索引

结合B+Tree的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率。

插入连续的数据:

插入非连续的数据:

28.分析创建索引时注意点

1.非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;
2.取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
3.索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。

29.分析需要注意联合索引中的顺序

MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。

30.分析非聚簇索引不一定会回表查询

不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。一个索引包含(覆盖)所有需要查询字段的值,被称之为"覆盖索引"。
举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select score from student where score > 90的查询时,在索引的叶子节点上,已经包含了score 信息,不会再次进行回表查询。

31.分析两条SQL语句哪个执行效率高

以下两条SQL语句,那个执行效率高? 为什么?
A. select * from user where id = 10 ;
B. select * from user where name = ‘Arm’ ;
备注: id为主键,name字段创建的有索引;
解答:
A 语句的执行性能要高于B 语句。
因为A语句直接走聚集索引,直接返回数据。 而B语句需要先查询name字段的二级索引,然后再查询聚集索引,也就是需要进行回表查询。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GoGo在努力

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

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

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

打赏作者

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

抵扣说明:

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

余额充值