草庐IT

【第三篇】MySQL 索引失效的常见原因【重点】

sunnyday0426 2023-05-15 原文

1.1 概述

   有时候不知道小伙伴有没有跟我一样的情况,明明已经建立了索引,但是通过explain发现语句并没有使用上索引,这可能是某些原因导致了我们的索引失效。所以本篇主要介绍的是索引失效的常见原因和如何用好索引,给有需要的朋友一个参考。

1.2 索引失效

1.2.1 索引失效常见原因

我们先来看一张图,总结了常见的索引失效的原因

1.2.2 索引失效常见误区

1.2.3 索引设计的几个建议

1.3 索引示例

1.3.1 准备工作

创建一张表 test1

CREATE TABLE `test1` (
  `id` bigint NOT NULL,
  `code` varchar(30) NOT NULL,
  `age` int NOT NULL,
  `name` varchar(30) NOT NULL,
  `height` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_code_age_name` (`code`,`age`,`name`) USING BTREE,
  KEY `idx_height` (`height`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

插入两条数据:

INSERT INTO `test1`(`id`, `code`, `name`, `age`,`address`) VALUES (1, '001', '张飞', 18,'7');
INSERT INTO `test1`(`id`, `code`, `name`, `age`,`address`) VALUES (2, '002', '关羽', 19,'8');

查询一下:

select * from test1;

结果:

此外建立了两个索引:idx_code_age_name( code , age , name )联合索引 和 idx_height(height)普通索引

1.3.2 索引失效常见原因

1.3.2.1 不满足最左前缀原则

1.3.2.1.1 第1种情况

where 条件后的字段包含了联合索引的所有索引字段,并且顺序是按照: code 、 age 、name
执行sql如下:

explain  select * from test1 
    where code='001' and age=18  and  name='张飞' ;

可以看出已经走了联合索引idx_code_name_age,并且索引的长度是 188, 188 = 30 * 3 + 2 + 30 * 3 + 2 + 4 ,索引是使用充分的,索引使用效率最佳

1.3.2.1.2 第2种情况

where 条件后的字段包含了联合索引的所有索引字段,顺序是不按照: code 、 age 、name。
执行sql如下:

explain  select * from test1 
    where code='001' and  name='张飞' and age=18;

可以看出执行结果跟第一种情况一样。
注意:这种情况比较特殊,在查询之前mysql会自动优化顺序

1.3.2.1.3 第3种情况

where 条件后的字段包含了联合索引中的: code 字段
执行sql如下:

explain  select * from test1 
    where code='001';

也走了索引,但是索引长度有所变化,现在变成了 92 , 92 = 30*3 + 2 ,只用到了一个索引字段code,索引使用不充分

1.3.2.1.4 第4种情况

where 条件后的字段包含了联合索引中的: age 字段
执行sql如下:

explain  select * from test1 
    where age=18;

从上图中看变成了全表扫描,所有的索引都失效了

1.3.2.1.5 第5种情况

where 条件后的字段包含了联合索引中的: name 字段
执行sql如下:

explain  select * from test1 
    where  name='张飞';

从上图中看变成了全表扫描,所有的索引都失效了

1.3.2.1.6 第6种情况

where 条件后的字段包含了联合索引中的: code 和 age 字段
执行sql如下:

explain  select * from test1 
   where code='001' and age=18;

从上图中看出还是走了索引,但是索引长度变成了: 96 , 96 = 30*3 + 2 + 4 ,只用到了两个索引字段code和age,索引使用也不充分。

1.3.2.1.7 第7种情况

where 条件后的字段包含了联合索引中的: code 和 name 字段
执行sql如下:

explain  select * from test1 
   where code='001' and  name='张飞';

从上图中看出走的索引长度跟第3种情况一样,长度也是 92 。也就是说只用到了一个索引字段 code ,而 age 字段的索引失效了

1.3.2.1.8 第8种情况

where 条件后的字段包含了联合索引中的: age 和 name 字段
执行sql如下:

explain  select * from test1 
   where age=18  and  name='张飞';

从上图中看出变成了全表扫描,所有的索引都失效了

1.3.2.2 小结

如果中间出现断层,如: code、name ,只会走第一个索引code,从断层后的索引都会失效。

SQL语句中字段的顺序不需要和联合索引中定义的字段顺序一致,查询优化器会自己调整顺序,mysql查询优化器会判断纠正这条sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。所以,当然是我们能尽量的利用到索引时的查询顺序效率最高咯,所以mysql查询优化器会最终以这种顺序进行查询执行

1.3.3 范围索引列没有放在最后

where 条件后的字段 age 用了大于等于,具体sql如下:

EXPLAIN  select * from test1 
where  code='001' and age>18  and  name='张飞' ;

从上图中看出索引长度变成: 96 , 96 = 30*3 + 2 + 4 ,只用到了两个索引字段 code 和age ,而 name 字段的索引失效了

如果范围查询的语句放到最后:

EXPLAIN  select * from test1 
where code='001' and  name='张飞' and age>18 ;

索引长度还是: 96

这是一个非常经典的错误
范围查询放最后是指创建联合索引的字段顺序,现在的顺序是:

调整一下把索引字段name和age的顺序调整一下:

再执行上面的sql,结果:

从上图中看出索引长度变成: 188 ,索引使用充分了。

回过头再执行刚开始的那条sql:

EXPLAIN  select * from test1 
where  code='001' and age>18  and  name='张飞';

索引长度也是: 188 。

注意范围查询放最后,指的是联合索引中的范围列放在最后,不是指where条件中的范围列放最后。如果联合索引中的范围列放在最后了,即使where条件中的范围列没放最后也能正常走到索引

1.3.4 使用了select *

其实在《阿里巴巴开发手册》中也明确说了,禁止使用select * 。

EXPLAIN  select * from test1

可以看出走了全表扫描。

那么如果查询的是索引列:

EXPLAIN  select code,age,name  from test1

可以看出这种情况走了全索引扫描,比全表扫描效率更高

其实这里用到了: 覆盖索引

如果 select 的列都是索引列,则被称为 覆盖索引

如果 select 的列不只包含索引列,则需要 回表 ,即回到表中再查询出其他列,效率相当更低一些。 select * 大概率需要查询非索引列,需要 回表 ,因此要少用

当然,本文中很多示例都使用了 select * ,主要是我表中只有两条数据,为了方便演示,正常业务代码中是要杜绝这种写法的

1.3.5 索引列上有计算

执行sql如下:

explain  select * from test1 
where  height+1 =7;

可以看出变成全表扫描了,由此可见在索引列上有计算,索引会失效。

1.3.5 索引列上使用了函数

如果在索引列加某个函数,具体sql如下:

explain  select * from test1 
where   SUBSTR(height,1,1)=8;

从上图中可以看出变成全表扫描了,由此可见在索引列上加了函数,索引也会失效。

1.3.6 字符类型没加引号

explain  select * from test1 
where  name = 123;

可以看出走的全表扫描,索引失效了。
name 字段是 字符类型 ,而等于号右边的是 数字类型 ,类型不匹配导致索引丢失

所以在使用字符类型字段做判断时,一定要加上单引号

类型不匹配导致索引丢失问题,是我们平时工作中非常容易忽视的问题,一定要引起足够的重视

1.3.7 用is null和is not null没注意字段是否允许为空

前面创建的test1表中height字段是非空的。

查询sql如下:

explain  select * from test1 
where  height is  null;

explain  select * from test1 
where  height is  not  null;

结果都是全表扫描,索引都失效了。

如果height字段改成允许为空的呢?

上面第一条sql执行结果:

看出走了 ref 类型的索引。

上面第二条sql执行结果:

看出走了 range 类型的索引。

1.3.7.1 小结

如果字段不允许为空,则is null 和 is not null这两种情况索引都会失效

如果字段允许为空,则is null走 ref 类型的索引,而is not null走 range 类型的索引

1.3.8 like查询左边有%

like查询主要有三种情况:

like ‘%a’
like ‘a%’
like ‘%a%’

1.3.8.1 第一种情况

explain  select * from test1 
where  code like '%001';

看出走的全表扫描,索引失效了。

1.3.8.2 第二种情况

explain  select * from test1 
where  code like  '001%';

看出走的 range 类型的索引

1.3.8.3 第三种情况

explain  select * from test1 
where  code like  '%001%';

看出走的全表扫描,索引也失效了

从这三种结果看出 like 语句只有 % 在右边才能走索引

如果有些场景就是要使用 like 语句 % 在左边该怎么办呢?

答案:使用覆盖索引

具体sql如下:

explain  select code,age,name  from test1 
where  code like  '%001%';

看出走的 index 类型的全索引扫描,相对于全表扫描性能更好

当然,最佳实践是在 sql 中要避免 like 语句 % 在左边的情况,如果有这种业务场景可以使用es 代替 mysql 存储数据

1.3.8.4 小结

like ‘%a’ 索引失效

like ‘a%’ 走range类型索引

like ‘%a%’ 索引失效

1.3.9 使用or关键字时没有注意

用法如下:

explain  select * from test1 
where  height = 8  or height = 9;

看出走了 range 类型的索引

再把sql改一下:

explain  select * from test1 
where  code = '001' or height = 8;

可以看出变成了全表扫描,索引失效了。

我们不妨单独查询一下:

explain  select * from test1 
where  code = '001';

explain  select * from test1 
where  height = 8;

两种单独查询的情况都走了 ref 类型的索引,但是使用 or 关键字后sql的索引会失效。

那么,我们在想使用 or 的场景,又想让索引有效,该怎么办呢?

explain (select * from test1 where   code = '001') 
union (select * from test1 where  height = 8);

没错,使用 union 关键字,但是跟 or 关键字的语法稍微有点区别,不过查询的数据结果是一样的。

上面sql执行结果如下:

走了 ref 类型索引。

or关键字会让索引失效,可以用union代替

1.4 索引失效的常见误区

1.4.1 使用not in会导致索引失效

用法如下:

explain  select * from test1 
where  height not  in (7,8);

看出是走了 range 类型索引的,并没失效

需要特别说明的是mysql5.7和5.8不同的版本效果不一样,5.7中这种情况sql执行结果是全表扫描,而5.8中使用了 range 类型索引

1.4.2 使用不等于号会导致索引失效

用法如下:

explain  select * from test1 
where height!=8;

看出走了 range 类型的索引。

需要特别说明的是mysql5.7和5.8不同的版本效果不一样,5.7中这种情况sql执行结果是全表扫描,而5.8中使用了 range 类型索引。5.7中如果想使用索引该怎么办呢?答案:使用大于和小于代替不等于

在这里温馨的提醒一声,不等于号不只是 != ,还包括 <> 。

1.4.3 order by索引字段顺序不当导致索引失效

sql中除了 where 后面的字段能走索引之外, order by 后面的字段也能走索引

EXPLAIN  select * from test1 
where  code='001' order  by age,name;

看出走了 ref 类型的索引,索引长度是 92 ,并且没有额外信息

但是如果把 order by 后面的条件改成如下两种排序:

EXPLAIN  select * from test1 
	where  code='001' order  by  name;
EXPLAIN  select * from test1 
	where  code='001' order  by  name,age;

看出还是走了 ref 类型的索引,索引长度是 92 ,但是额外信息中提示: Using filesort ,即按文件重排序。

上面两个例子能够看出有没有使用索引跟 where 后面的条件有关,而跟 order by 后面的字段没关系

而需不需要按文件重排序,则跟 order by 后面的字段有直接关系

问题来了,额外信息中提示: Using filesort 这种该如何优化?

答:这种情况一般是联合索引中索引字段的顺序,跟 sql 中 where 条件及 order by 不一致导致的,只要顺序调整一致就不会出现这个问题。

1.5 索引设计建议

优先使用唯一索引,能够快速定位

为常用查询字段建索引

为排序、分组和联合查询字段建索引

一张表的索引数量不超过5个

表数据量少,可以不用建索引

尽量使用占用空间小的字段建索引

用idx_或unx_等前缀命名索引,方面查找

删除没用的索引,因为它会占一定空间

1.6 特别说明

索引失效除了上述的常见问题之外,mysql 通过索引扫描的行记录数超过全表的10%~30% 左右,优化器也可能不会走索引,自动变成全表扫描

有关【第三篇】MySQL 索引失效的常见原因【重点】的更多相关文章

  1. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  2. git使用常见问题(提交代码,合并冲突) - 2

    文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g

  3. ruby-on-rails - 协会的 Rails 索引 - 2

    我发现自己需要这个。假设cart是一个包含用户列表的模型。defindex_of_itemcart.users.each_with_indexdo|u,i|ifu==current_userreturniendend获取此类关联索引的更简单方法是什么? 最佳答案 indexArray上的方法与您的index_of_item方法相同,例如cart.users.index(current_user)返回数组中第一个对象的索引==给obj。如果未找到匹配项,则返回nil。 关于ruby-on-

  4. ruby - Rails -- :id attribute? 所需的数据库索引 - 2

    因此,当我遵循MichaelHartl的RubyonRails教程时,我注意到在用户表中,我们为:email属性添加了一个唯一索引,以提高find的效率方法,因此它不会逐行搜索。到目前为止,我们一直在根据情况使用find_by_email和find_by_id进行搜索。然而,我们从未为:id属性设置索引。:id是否自动索引,因为它在默认情况下是唯一的并且本质上是顺序的?或者情况并非如此,我应该为:id搜索添加索引吗? 最佳答案 大多数数据库(包括sqlite,这是RoR中的默认数据库)会自动索引主键,对于RailsMigration

  5. ruby - 将对象设置为 nil 是否很常见? - 2

    我正在构建一个应用程序,想知道是否将未使用的对象设置为nil是生产级编码中的常见做法。我知道这只是垃圾收集器的提示,并不总是处理对象。 最佳答案 根据这个thread如果您使用完一个成员对象,将其设置为nil将引发被引用对象被垃圾回收。如果它是局部变量,方法exit将做同样的事情。也就是说,如果您要求将成员显式设置为nil,我会质疑您的设计。 关于ruby-将对象设置为nil是否很常见?,我们在StackOverflow上找到一个类似的问题: https://

  6. ruby-on-rails - 无法安装 mysql2 0.3.14 gem - 2

    我看到其他人也遇到过类似的问题,但没有一个解决方案对我有用。0.3.14gem与其他gem文件一起存在。我已经完全按照此处指示完成了所有操作:https://github.com/brianmario/mysql2.我仍然得到以下信息。我不知道为什么安装程序指示它找不到include目录,因为我已经检查过它存在。thread.h文件存在,但不在ruby​​目录中。相反,它在这里:C:\RailsInstaller\DevKit\lib\perl5\5.8\msys\CORE\我正在运行Windows7并尝试在Aptana3中构建我的Rails项目。我的Ruby是1.9.3。$gemin

  7. ruby - 变量赋值后的 if 语句 - 有多常见? - 2

    我最近与一位同事讨论了以下Ruby语法:value=ifa==0"foo"elsifa>42"bar"else"fizz"end我个人并没有看到太多这种逻辑,但我的同事指出,这实际上是一种相当普遍的Rubyism。我试着用谷歌搜索这个主题,但没有找到任何文章、页面或SO问题来讨论它,这让我相信这可能是一种非常实际的技术。然而,另一位同事发现语法令人困惑,而是将上面的逻辑写成这样:ifa==0value="foo"elsifa>42value="bar"elsevalue="fizz"end缺点是value=的重复声明和隐式elsenil的丢失,如果我们想使用它的话。这也感觉它与Ruby

  8. ruby - 如何使用 ruby​​ mysql2 执行事务 - 2

    我已经开始使用mysql2gem。我试图弄清楚一些基本的事情——其中之一是如何明确地执行事务(对于批处理操作,比如多个INSERT/UPDATE查询)。在旧的ruby-mysql中,这是我的方法:client=Mysql.real_connect(...)inserts=["INSERTINTO...","UPDATE..WHEREid=..",#etc]client.autocommit(false)inserts.eachdo|ins|beginclient.query(ins)rescue#handleerrorsorabortentirelyendendclient.commi

  9. ruby - 引用具有指定索引的枚举器值 - 2

    假设我有一个可枚举对象enum,现在我想获取第三个项目。我知道一种通用方法是转换成数组,然后使用索引访问,如:enum.to_a[2]但这种方式会创建一个临时数组,效率可能很低。现在我使用:enum.each_with_index{|v,i|breakvifi==2}但这非常丑陋和多余。执行此操作最有效的方法是什么? 最佳答案 你可以使用take剥离前三个元素,然后剥离last从take给你的数组中获取第三个元素:third=enum.take(3).last如果您根本不想生成任何数组,那么也许:#Ifenumisn'tanEnum

  10. ruby - 尝试比较两个文本文件,并根据信息创建第三个 - 2

    我有两个文本文件,master.txt和926.txt。如果926.txt中有一行不在master.txt中,我想写入一个新文件notinbook.txt。我写了我能想到的最好的东西,但考虑到我是一个糟糕的/新手程序员,它失败了。这是我的东西g=File.new("notinbook.txt","w")File.open("926.txt","r")do|f|while(line=f.gets)x=line.chompifFile.open("master.txt","w")do|h|endwhile(line=h.gets)ifline.chomp!=xputslineendende

随机推荐