【MySQL】SQL索引失效的几种场景及优化

MySQL中提高性能的一个最有效的方式是对数据表设计合理的索引。索引提供了高效访问数据的方法,并且加快查询的速度, 因此索引对查询的速度有着至关重要的影响。

使用索引可以快速地定位表中的某条记录,从而提高数据库查询的速度,提高数据库的性能。如果查询时没有使用索引,查询语句就会扫描表中的所有记录。在数据量大的情况下,这样查询的速度会很慢。

我们一般创建的索引类型都是B+Tree结构,其实,用不用索引最终都是优化器说了算。

那么MySQL优化器是什么呢?

MySQL内部优化器是MySQL中很重要的一个部分,它主要用于在执行查询时获取最合适的执行计划,以使得查询能够以最短的时间内得到结果。

MySQL内部优化器的工作原理是在接收到一条查询语句之后,它会根据一系列的算法和规oSi则来确定哪个执行计划是最优的。

通常情况下,MySQL优化器会依赖于表的统计信息和索引信息来进行优化决策。例如,在执行select语句时,优化器会尝试使用索引来避免全表扫描。同时,优化器还会对各种查询操作的代价进行估算,以便找到最优的执行计划。

在这里插入图片描述

那么我们如何去查看这条sql的一个执行计划呢?很简单只需要在我们执行的sql前面加上explain关键字即可。

-- 创建数据库表格
CREATE TABLE `account` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `phone` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `pwd` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `province` varchar(80) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `city` varchar(60) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `status` int NOT NULL,
  `gmt_create` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- 执行sql查看执行计划
explain SELECT * from account where id = '1'

在这里插入图片描述

EXPLAIN 是用于分析并优化查询语句性能的工具,执行计划会解析查询语句并生成执行计划,包括访问表和索引所需的策略、查询优化器的选择以及每个阶段的估计记录数等信息,深入SQL语句在执行过程中的各个细节。

OK,接下来我们来看一下各个字段的含义。

id:对于 SELECT 语句,每个查询都会被分配一个唯一的ID。表示查询的标识符,数字越大越先执行。

select_type:表示查询类型或者子查询类型,使用不同的 select_type 来帮助评估查询性能,并确定可以采取哪些优化方法。需要根据具体情况来进行相应的优化,例如尽量减少子查询的数量,避免使用不必要的 UNION 操作等等。

类型 概述
SIMPLE 表示简单的 SELECT 查询,不包含子查询或 UNION 操作。
PRIMARY 表示外层查询的第一个 SELECT
UNION 表示 UNION 操作的第二个或后续的 SELECT 查询。
SUBQUERY 表示一个子查询,MySQL 会在子查询中先执行查询,比如where里面包括了子查询
DEPENDENT SUBQUERY 也表示一个子查询,但是外部 SQL 查询的结果会影响子查询的执行
DERIVED 表示派生表,MySQL 会在查询中创建一个新的临时表,这个临时表来自于 FROM 子句中的子查询
UNION RESULT 表示 UNION 操作的结果,MySQL 在创建结果集时使用临时表来存储数据

table:表示查询涉及到哪些表,对于子查询等复杂查询可能涉及多张表。

partitions:表示查询操作涉及到的分区表的分区情况。

type:表示 MySQL 在表中找到所需行的方式,常见的类型包括 ALL, index,range, ref, eq_ref, const, system, NULL。

Type 概述
all 全表扫描,MYSQL扫描全表来找到匹配的行
index 索引全扫描,MYSQL遍历整个索引来查找匹配的行;Extra 字段里面 出现 Using index,则是覆盖索引,不用二次回表查询
range 索引范围扫描,常见于<、<=、>、>=、between、in等操作符;相对于index的全索引扫描,它有范围限制,因此要优于index
ref 使用非唯一性索引或者唯一索引的前缀扫描,返回匹配某个单独值的记录行;
虽使用了索引但该索引列的值并不唯一,进行目标值附近的小范围扫描,不扫描全表
eq_ref eq_ref 与 ref对比结果集只有一个,使用主键或者唯一索引进行查找,不用扫描更多行
const 最多只有一条匹配行,查询非常迅速,用到primary key 或者unique key,性能最高
system 表只有一行,基本不会出现,忽略
null 不访问数据库表,直接返回索引

possible_key:表示 MySQL 可以使用哪些索引来优化查询

key:表示 MySQL 实际使用的索引,如果没有使用任何索引,则该值为 NULL

key_len:表示 MySQL 实际使用的索引的长度,该值与索引定义的长度有关

ref:表示 MySQL 使用哪个列或常量与索引列进行比较。

rows:表示 MySQL 估计要扫描多少行才能找到所需记录,是一个估算值而不是确切值。

filtered:查询条件过滤的效率,百分比形式表示, Filtered 越高,表示查询结果集中过滤数据所需要的开销越小,查询性能就越好。

Extra:该字段包括一些额外的查询信息,包括使用何种排序方式、使用哪种 Join 操作等。

类型 概述
Using index 选择使用了覆盖索引的特性,通过索引直接获取查询结果,而无需回表查询,提高了查询效率。
Using filesort 需要额外进行 一个文件排序操作来实现 ORDER BY 操作,可能会严重影响查询性能。
Using temporary 在执行查询时需要借助临时表来保存中间结果集,这常发生在排序、分组、子查询和 UNION 查询之中。
Using where 条件查询,在查询过程中需要进行表级别的条件过滤,即使共享了某些索引,也需要进行全表扫描查找符合条件的行。不是仅仅通过索引就可以获取所有需要的数据,则会出现 Using where
Range checked for each record 通过索引比较操作来过滤部分行,直到找到符合条件的行,这种操作常出现在使用 INDEX 和 ORDER BY 操作时。
Using join buffer (Block Nested Loop) 在执行连接操作时需要额外申请 join buffer 来存储中间结果,这种操作常发生在连接操作中。
Using index condition 利用了查找索引数据的过程中额外发现的过滤条件进行了优化,无需回表查询或查表,可以直接通过索引结果来返回查询的结果
Using sort_union()Using union() 通过 UNION ALL 或 UNION DISTINCT 操作来合并查询结果集,使用了一些优化策略来提高查询效率。

OK,介绍了这么多,下面我们就开始进入正题,来说一说索引失效的场景都有哪些。

1.隐式转换导致不走索引,索引失效

当采用索引查询时列的类型不一样,就会导致索引失效。我们当前account表中id是varchar类型,我们现在查询用数字类型查,这会就会导致索引失效。

explain SELECT * from account where id = 1

在这里插入图片描述

改用字符串查询

explain SELECT * from account where id = '1'

在这里插入图片描述

2.当索引列配合不是索引列进行or查询时,索引失效

当我们查询时索引列配合不是索引列进行查询的时候,会导致索引失效,比如说id是索引,gmt_create不是索引,当**id = ‘1’ or gmt_create = ‘2024-01-01’**时,这就会导致索引失效。

explain SELECT * from account where id='1' or  gmt_create = '2024-01-01'

在这里插入图片描述

要想让其走索引查询,可以给gmt_create加上索引,or两边字段都是索引字段才会走索引

CREATE INDEX gmt_create on account(gmt_create)

在这里插入图片描述

这里也可以我们规定强制走哪一个索引,不过一般不建议,因为sql优化器已经帮我们计算好最优的查询方式。

explain SELECT * from account force index(PRIMARY) where id='1' or  gmt_create = '2024-01-01'

在这里插入图片描述

虽然强制地使用了索引,但是经过分析,这次查询还是没有使用索引,所以强制使用索引并不一定是生效的。

3.业务表的数据量太少,索引失效

MySQL索引是为了加速查询而存在的,如果数据量太小,MySQL查询速度本来就很快,这时候使用索引反而会拖慢查询速度。因此,当数据量很小的时候,MySQL索引可能会失效。

explain SELECT * from account where id = '1'

在这里插入图片描述

4.当索引字段采用函数查询时,索引失效

当索引字段采用函数查询时,会导致索引失效,比如gmt_create本身是一个索引字段,我们采用YEAR函数进行查询,就会导致索引的失效。

explain SELECT * from account where YEAR(gmt_create)  =  '2023'

在这里插入图片描述

5.like查询索引字段左边模糊查询,索引失效

当索引列使用LIKE操作符时,左边模糊查询会导致索引失效。比如我们给province加上索引,我们用province like “%天津%” 或者 province like “%天津” 都会导致索引的失效,只有province like "天津%"索引才不会失效。

添加索引:CREATE INDEX province on account(province)

explain SELECT * from account where province like '%天津'

在这里插入图片描述

explain SELECT * from account where province like '天津%'

在这里插入图片描述

6.字段重复性高导致索引失效

比如有一些字段他的重复性的值确实特别的高,那么这种字段就不适合加索引。

在这里插入图片描述

explain SELECT * from account where province = '宝地区'

在这里插入图片描述

7.IS NULL操作时,索引失效

IS NULL不走索引,IS NOT NULL走索引,设计字段的时候,如果没有要求必须为NULL,那最好给个默认值空字符串。

explain SELECT id from account where province is  NULL

在这里插入图片描述

explain SELECT id from account where province is not NULL

在这里插入图片描述

还有一种情况,单键值的B树索引列上存在null值,导致COUNT(*)不能走索引。

-- status状态加上索引
CREATE INDEX status on account(status)

在这里插入图片描述

explain SELECT count(status) from account

我们来看一下status加上索引,没有为空的数据时,执行计划是啥样的。

在这里插入图片描述

8.联合索引没有遵循最左匹配原则,索引失效

如果使用了联合索引,但查询时未使用索引的第一列,索引也会失效。

原因:比如我们根据字段(t1,t2,t3)建立了联合索引,则排序规则是先按t1字段进行排序,t1字段相同再按t2字段排序,当t1、t2字段都相同时再按t3字段进行排序。如果我们的查询条件中没有使用到第一列,那么该索引也就没有办法使用。

--  创建联合索引
CREATE INDEX idx_phone_provice_status on account(phone,province,status)

我们删除之前加的province和status的单独的索引。

explain select * from account where province = '宝地区' and status = 1

在这里插入图片描述

只要我们把联合索引的第一列放在前面,就可以生效。

explain select * from account where phone = '12384374374' and province = '宝地区' and status = 1

在这里插入图片描述

explain select * from account where phone = '12384374374' and status = 1

在这里插入图片描述

9.不等于操作符(<>、!=)会导致索引失效

这种查询语句无法使用索引,因为需要扫描整个表来查找不等于’value’的记录。

explain select * from account where phone != '12384374374'

在这里插入图片描述

10.IN语句引起的索引失效

使用IN语句进行查询时,如果查询的值列表比较大或者是一个子查询,则会引起索引失效。

我们可以通过以下sql来模拟这种情况,这块就不做真实的演示啦,大家在工作中遇到IN查询的时候可以看一下执行计划,然后做出对应的调整。

SELECT * FROM table_name WHERE column_name IN (SELECT column_name FROM another_table);

这样的查询语句会导致数据库无法使用索引来查找匹配的记录,因为索引只能查找单个值,而不能匹配多个值。

为了避免IN语句导致的索引失效,我们可以使用以下替代方案:

使用EXISTS语句来代替IN语句,例如:

SELECT * FROM table_name1 t1 WHERE EXISTS (SELECT * FROM table_name2 t2 WHERE t2.column_name = t1.column_name);

或者是使用JOIN来代替IN语句,例如:

SELECT * FROM table_name1 t1 JOIN table_name2 t2 ON(t1.column_name = t2.column_name);

11.数据库与表还有表与表的编码不兼容,索引失效

在sql中做表关联时,需要注意两边字段的编码要保持一致。

Ok,以上就是我们在工作中常见的一些索引失效的案例。

接下来我们来说一下,索引的一些设计规则。

  • 高频次查询且数据量大的表建立索引
  • 经常需要排序、分组和联合操作的字段建立索引
  • 短索引可以提升访问的IO效率,对于BLOB、TEXT或很长的varchar列使用前缀索引
  • 删除无用索引,同列上创建多个索引,越多索引维护成本越高,优化器在优化查询时也需要逐个考虑,会影响性能
  • 根据业务需求,设计好联合索引,业务使用的时候尽量用到联合索引,避免回表查询
  • 尽量选择区分度高的列作为索引,区分度越高性能越好,比如唯一索引
  • 索引列不参与计算,带函数的查询不建议做为索引列
  • 尽量扩展利用现有索引,联合索引的查询效率比多个独立索引高
  • 尽量避免NULL,应该指定列为NOT NULL,含有空值的列很难进行查询优化,可以用0或一个空串代替NULL
  • 唯一索引与普通索引
    • 唯一索引和普通索引在性能上没有本质的区别,但在数据的唯一性方面
    • 唯一索引在数据插入和更新时需要更多计算,因此略微慢一些。
  • 聚簇索引与非聚簇索引
    • 聚簇索引在性能上优于非聚簇索引,因为聚簇索引是将数据存储在一起的
    • 这样检索数据时可以最大程度地减少磁盘 IO 操作。但如果经常更新表中的数据,则聚簇索引的维护成本相对较高。
  • 覆盖索引与非覆盖索引
    • 覆盖索引可以直接从索引中获取数据,无需回表查询,因此执行速度更快
    • 但是如果查询需要取出的数据列不在索引中,则无法使用覆盖索引,需要进行回表查询,效率较低。

好啦,至此本文就到这啦,记得三连➕关注哦!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_47533244/article/details/131617889