Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL索引不生效

MySQL索引不生效的8种原因与解决方法

作者:师兄奇谈

在数据库优化中,最让人头疼的事情之一莫过于精心设计的索引没有发挥作用,本文将整理8个MySQL索引不生效的原因与解决方法,需要的小伙伴可以了解下

在数据库优化中,最让人头疼的事情之一莫过于精心设计的索引没有发挥作用。为什么会出现这种情况?

这篇文章带大家一起探讨一些常见原因,方便大家更好地理解MySQL查询优化器是如何选择索引的,以及在出现类似问题时,可逐项进行对照排查,

以一个简单的 people 表作为例子,表结构如下:

CREATE TABLE `people` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `first_name` varchar(50) NOT NULL,
  `last_name` varchar(50) NOT NULL,
  `state` char(2) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `first_name` (`first_name`),
  KEY `state` (`state`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

后续会以该表结构为基础,通过添加或删除索引来展示不同场景。

确认索引是否被使用

在分析索引未生效的原因之前,首先需要判断 MySQL 是否使用了索引。可以通过 EXPLAIN 命令来查看查询优化器的分析结果,了解哪些索引被考虑,以及最终选择使用了哪个索引。

例如,以下查询会试图通过 first_name 索引查找数据:

EXPLAIN SELECT * FROM people WHERE first_name = 'Aaron';

返回结果如下:

idtabletypepossible_keyskeykey_lenrefrowsfilteredExtra
1peoplereffirst_namefirst_name202const180100.00

从结果中可以看到:

关于EXPLAIN 的使用,可参考文末补充内容

在本例中,first_name 索引不仅被优化器考虑(considered),而且最终被选中(chosen)。这是两个相关但不同的步骤:首先,优化器会根据查询筛选可用的索引;然后,选择性能较优的索引。

确认索引是否被使用后,接下来分析一些索引未生效的常见原因。

索引未生效的原因

原因 1:另一个索引更优

当查询可以利用多个索引时,MySQL 优化器会选择其中最优的索引。如果你的查询可以同时使用多个索引,但最终未选择预期的索引,很可能是因为另一个索引的效率更好。

例如,以下查询同时使用 first_name 和 state 字段:

SELECT * FROM people
WHERE first_name = 'Aaron'
AND state = 'TX';

运行 EXPLAIN 后结果如下:

idtabletypepossible_keyskeykey_lenrefrowsfilteredExtra
1peoplereffirst_name,statefirst_name202const18050.00Using where

在这个例子中,first_name 索引比 state 索引的选择性更高,因此优化器选择了 first_name 索引。

原因 2:索引的选择性和基数

索引的性能往往与选择性和基数相关:

比如,可以通过以下查询计算基数和选择性:

SELECT
  COUNT(DISTINCT first_name) as first_name_cardinality,
  COUNT(DISTINCT state) as state_cardinality,
  COUNT(DISTINCT first_name) / COUNT(*) as first_name_selectivity,
  COUNT(DISTINCT state) / COUNT(*) as state_selectivity
FROM people;

结果如下:

first_name_cardinalitystate_cardinalityfirst_name_selectivitystate_selectivity
300920.00600.0000

高选择性索引通常性能较优,而低选择性索引在过滤数据时作用有限。

此外,唯一索引(如 id 的主键索引)通常具有完美选择性。

原因 3:选择性因查询而异

索引的选择性是基于整个表数据分布进行计算的,但选择性在具体查询场景中可能不一样。例如:

假如表中有 100 万行,其中 99% 的用户类型是 user,只有 1% 为 admin,总体来看 type 列选择性很低。但如果你的查询条件是 type = 'admin',此时索引的作用就很明显。

优化器会根据查询条件和数据分布动态评估索引的价值。

原因 4:过时或不准确的统计数据

MySQL 的索引基数统计信息是通过随机采样维护的,可能出现因统计信息过时而导致优化器做出错误决策的情况。可以通过以下命令更新统计信息:

ANALYZE TABLE people;

如果统计数据采样精度不足,可以通过调整 MySQL 的相关参数改善采样质量。

原因 5:表扫描更快

某些情况下,优化器会选择直接扫描整个表而不是使用索引。这可能发生在以下场景:

虽然表扫描看起来反直觉,但在特定情况下确实更高效。

原因 6:索引的结构性限制

理解索引的底层结构(如 B+ 树),有助于分析某些查询为什么无法用到索引。主要有以下几个场景:

场景 1:通配符搜索

MySQL 的索引只能用于匹配字符串的前缀部分,不能用于字符串中的后缀或包含部分。例如:

如果你需要复杂的字符串搜索,可以考虑使用全文索引(Fulltext Index)或专门设计的数据模型。

场景 2:复合索引的左前缀规则

复合索引要求使用时遵循“左前缀”规则,例如:

ALTER TABLE people ADD INDEX multi (first_name, state);

场景 3:连接列类型或字符集不匹配

若连接的字段类型或字符集不一致,索引将无法生效。例如:

确保字段定义一致是索引生效的前提。

原因 7:索引被模糊化处理

某些查询因对字段使用了函数或运算导致索引无法使用。例如:

SELECT * FROM people WHERE YEAR(created_at) = 2023;

上述查询无法使用 created_at 索引,因为 MySQL 没法直接基于函数计算进行优化。替代方案如下:

SELECT * FROM people WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31';

通过范围查询可以正常使用索引。

原因 8:隐藏索引

MySQL 支持隐藏索引,隐藏索引不会被查询优化器使用。例如:

ALTER TABLE people ALTER INDEX first_name INVISIBLE;

Hidden 索引可以用于测试索引删除的影响,若查询性能下降可以随时恢复索引。

强制使用索引

如果你认为 MySQL 优化器的决策不正确,可以通过 USE INDEX 提示优化器使用指定索引:

EXPLAIN SELECT * FROM people USE INDEX (state) WHERE first_name = 'Aaron' AND state = 'TX';

但使用 USE INDEX 应该谨慎,因为可能在数据量增长后需要重新评估是否强制使用某索引。

知识补充

仅仅会用MySQL的EXPLAIN还不够,还需要会用EXPLAIN ANALYZE

在 MySQL 中,EXPLAIN 是一个关键字,用于了解查询执行的相关信息。本文将展示如何利用MySQL EXPLAIN 来解决查询中的性能问题。

虽然执行一个 EXPLAIN 计划相对简单,但其输出结果并不总是直观的。只有了解其功能,才能充分利用它来实现SQL语句的性能提升。

EXPLAIN 与 EXPLAIN ANALYZE 的区别

当在查询的前面添加 EXPLAIN 关键字时,它会解释数据库如何执行该查询以及估算的成本。

示例EXPLAIN语句:

通过利用这个MySQL 内部工具,可以观察到以下内容:

总之,通过使用 EXPLAIN,可以获得查询预期运行的步骤列表。

什么是 EXPLAIN ANALYZE

在 MySQL 8.0.18 中,MySQL 引入了 EXPLAIN ANALYZE,一个在常规 EXPLAIN 查询计划工具之上的新功能。除了列出查询计划和估算的成本,EXPLAIN ANALYZE 还打印了执行计划中各个迭代器的实际成本。

示例EXPLAIN ANALYZE语句:

注意事项:EXPLAIN ANALYZE 实际上会运行查询,因此如果你不希望查询在实时数据库上运行,请不要使用 EXPLAIN ANALYZE。

对于每个迭代器,EXPLAIN ANALYZE 提供以下信息:

MySQL EXPLAIN ANALYZE 的结果会显示查询运行前规划器的估算数据(如图中黄色突出显示部分)和查询实际运行后的数据(如绿色突出显示部分)。

EXPLAIN ANALYZE 的格式

EXPLAIN ANALYZE 可用于 SELECT 语句、多表 UPDATE 语句、DELETE 语句和 TABLE 语句。它会自动选择 FORMAT=tree 并执行查询(不会向用户显示任何输出)。EXPLAIN ANALYZE 专注于查询执行的关系以及部分查询的执行顺序。

EXPLAIN 输出以节点形式组织。在最低层,节点会扫描表或搜索索引;较高层的节点则操作来自低层节点的结果。

虽然 MySQL CLI 能以表格、制表符、垂直格式,以及漂亮或原始 JSON 格式打印 EXPLAIN 结果,但目前 EXPLAIN ANALYZE 不支持 JSON 格式。

EXPLAIN 和EXPLAIN ANALYZE的使用场景

当你不确定查询是否高效运行时,可以(且应)使用 EXPLAIN 查询。如果你认为已经正确索引并分区了表,但查询依旧运行缓慢,则可能需要使用EXPLAIN ANALYZE了。当查询进行EXPLAIN ANALYZE之后,就需要关注的输出内容以及优化目标了。

1.索引相关列:keys、possible keys 和 key lengths

在 MySQL 中处理索引时,需关注 possible_keys、key 和 key_len 列。

这些信息对设计索引、为特定任务决定使用何种索引,以及处理相关问题(如选择覆盖索引的适当长度)非常实用。

2.FULLTEXT 索引与连接

当使用 FULLTEXT 索引确保查询参与 JOIN 操作时,需注意 select_type 列,该列的值应为 fulltext。

3.分区

如果表已添加分区并希望查询使用这些分区,要观察 partition 列。如果 MySQL 实例正在使用分区,在大多数情况下,MySQL 会自动处理所有查询,而无需额外操作。如果希望查询使用特定分区,可以使用类似 SELECT * FROM TABLE_NAME PARTITION(p1,p2) 的查询。

EXPLAIN 的局限性

EXPLAIN 是一种估算工具。它有时是一个比较准确的估算,但有时可能非常不精确。以下是一些局限性:

SHOW WARNINGS 语句

需要注意的一点是:如果你用 EXPLAIN 的查询未正确解析,可以输入 SHOW WARNINGS; 查看最后一个运行的非诊断语句的信息。虽然它无法提供像 EXPLAIN 那样的查询执行计划,但它可能提供关于可处理的查询片段的线索。

SHOW WARNINGS; 包含一些特殊标记,其中信息可能包括:

MySQL EXPLAIN 的连接类型

MySQL 手册提到 type 列显示“连接类型”,用以解释表的连接方式。但实际上更准确的说法是“访问类型”,即告诉我们 MySQL 决定如何在表中找到行。

以下列出从性能最佳到最差的重要访问方式:

还有一些其他类型需要了解:

EXPLAIN 的 EXTRA 列

EXTRA 列包含其他列中未涵盖的额外信息。以下是一些重要值及其定义:

EXPLAIN 优化查询的实践示例

下面通过一个实践案例来演示一下使用 MySQL EXPLAIN 优化查询的方法。

1.运行初始查询

在开始之前,先创建一个数据库,并使用MySQL员工样例数据库进行初始化。

通过使用多列索引和MySQL EXPLAIN,允许数据库引擎联合使用多列加速查询。

例如,优化下列查询:

SELECT * FROM employees WHERE last_name = 'Puppo' AND first_name = 'Kendra';

在运行该查询后,EXPLAIN 的结果显示访问了 299,733 行,而这是我们需要优化以提升性能的根本原因。

优化方法 1:创建两个独立索引

一种方法是分别为 last_name 列和 first_name 列创建单独索引,但这种方式有一个问题——MySQL 知道如何找到所有姓 Puppo 的员工,也知道如何找到所有名为 Kendra 的员工,但却无法同时高效找到名为 Kendra Puppo 的员工。

其他需要注意的事项:

优化方法 2:使用多列索引

由于第一种方法的问题,我们知道需要找到一种解决方案来使用能够考虑多列的索引。这里我们可以使用多列索引来实现这一目标。

可以将其想象成一本电话簿嵌套在另一本电话簿中。首先,查阅姓氏 “Puppo”,然后进入第二个目录,该目录按名字的字母顺序组织所有名为 “Kendra” 的人,在这个目录中可以快速找到“Kendra Puppo”。

在 MySQL 中,若要为 employees 表中的姓氏和名字创建多列索引,可以执行以下命令:

CREATE INDEX fullnames ON employees(last_name, first_name);

现在,多列索引已成功创建,我们可以执行以下 SELECT 查询来查找名字为 Kendra 且姓氏为 Puppo 的记录。结果将是一行数据,其中包含名为 Kendra Puppo 的员工信息。

使用 EXPLAIN 来检查该查询是否使用了索引:

查询优化后的结果显示,索引被使用,并且只访问了一行数据来完成请求。这比优化前必须访问 299,733 行要高效得多。

总结

索引优化涉及多个方面,包括查询优化器运作、数据分布、索引结构等。了解索引未生效的原因并合理优化查询,可以显著提升数据库性能。索引虽强大,但只有正确规划和使用才能发挥最大效用。

到此这篇关于MySQL索引不生效的8种原因与解决方法的文章就介绍到这了,更多相关MySQL索引不生效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文