视频讲解:SQL优化:SQL执行计划详细分析_哔哩哔哩_bilibili
1.1 执行计划详解
|----|-------------|-------|------------|------|---------------|-----|---------|-----|------|----------|-------|
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| | | | | | | | | | | | |
| | | | | | | | | | | | |
1.1.1 ID
【概念解析】
id 列代表了SELECT查询的标识符,它表示的是查询执行的顺序或层级。id 的值有以下含义:
**相同的 id:**如果多个操作有相同的 id 值,则它们是同级操作,并且通常是从上到下执行的(但也有例外)。
**不同的 id:**如果 id 值不同,则表示子查询或嵌套查询。通常来说,具有更高 id 值的操作依赖于较低 id 值的操作的结果。
**递增的 id:**当 id 值递增时,这通常意味着前一个操作的结果被用来作为后一个操作的基础。
【相同ID的例外情况】
当多个操作具有相同的 id 值时,它们通常是同级操作,这意味着它们属于同一个 SELECT 语句并且通常按照从上到下的顺序执行。然而,在某些情况下,MySQL 可能会改变这个执行顺序来优化查询性能。这些例外情况主要与 MySQL 查询优化器的行为有关,具体来说包括但不限于以下几种情况:
并行处理:
MySQL 可能在某些情况下选择并行执行同级操作来提高性能。例如,如果两个表都具有合适的索引,MySQL 可能会选择同时从这两个表中读取数据。
延迟关联(Delayed Join):
MySQL 的查询优化器可能会选择延迟关联表,这意味着它可能会先处理一个表,然后再处理另一个表,即使它们的 id 相同。这种优化可以减少中间结果集的大小,从而提高查询效率。
循环连接(Loop Join):
当涉及到多表连接时,MySQL 可能会以不同的顺序访问表,以找到最佳的执行路径。这种情况下,即使是同级操作也可能不会按照从上到下的顺序执行。
临时表的使用:
如果查询优化器决定使用临时表来存储中间结果,那么即使这些操作具有相同的 id,执行顺序也可能会发生变化。
合并排序:
当需要对多个结果集进行排序时,MySQL 可能会先独立地对每个结果集进行排序,然后再合并这些已排序的结果集。这种情况下,即使操作具有相同的 id,实际执行顺序也可能有所不同。
半连接优化(Semi-Join Optimization):
MySQL 可能会应用半连接优化,先处理较小的表,然后使用结果去过滤较大的表。这种优化可以减少连接操作的成本。
索引合并(Index Merge):
如果查询涉及多个索引,MySQL 的查询优化器可能会使用索引合并策略,而不是分别扫描每个索引,再进行合并。
索引条件推送(Index Condition Pushdown, ICP):
MySQL 会在某些版本中使用 ICP 技术,它可以将 WHERE 子句中的条件直接应用到索引扫描上,从而减少需要检索的数据量。这可能会影响到执行顺序。
覆盖索引(Covering Indexes):
如果某个索引包含了查询所需的全部列,MySQL 可能会直接从该索引中获取所有需要的数据,而不需要回表查询更多的数据。
1.1.2 select_type
select_type 可以帮助识别哪些部分是主查询、哪些是子查询等。
SIMPLE:
这个值表示查询是最简单的 SELECT 语句,没有子查询或 UNION。
通常,只有一个查询块会被标记为 SIMPLE。
PRIMARY:
当查询包含子查询时,最外层的查询块会被标记为 PRIMARY。
它表示整个查询的主体部分。
UNION:
当查询使用了 UNION 时,除了第一个 SELECT 语句之外的所有 SELECT 语句都会被标记为 UNION。
UNION 类型的查询块依赖于 PRIMARY 查询块的结果集。
DEPENDENT UNION:
类似于 UNION,但当 UNION 中的 SELECT 语句依赖于外部查询的结果时,它们会被标记为 DEPENDENT UNION。
这种类型的查询块每次外部查询执行时都会重新计算。
UNION RESULT:
标记为 UNION RESULT 的查询块表示 UNION 或 UNION ALL 结果的最终合并。
它通常出现在 UNION 或 UNION ALL 后面,用于标识合并操作。
SUBQUERY:
当查询包含一个不在 FROM 子句中的子查询时,该子查询会被标记为 SUBQUERY。
SUBQUERY 类型的查询块独立于外部查询执行。
DEPENDENT SUBQUERY:
当子查询依赖于外部查询的结果时,该子查询会被标记为 DEPENDENT SUBQUERY。
这种类型的子查询在外部查询的每一行上都会被重新计算。
DERIVED:
当查询包含一个派生表(即在 FROM 子句中定义的子查询)时,该子查询会被标记为 DERIVED。
MySQL 会首先执行派生表的查询,并将其结果放入一个临时表中,然后使用这个临时表作为外部查询的一部分。
MATERIALIZED:
类似于 DERIVED,但表示一个已经物化的子查询结果,即结果被存储在一个临时表中。
这种类型通常用于存储子查询的结果以便重复使用。
UNNEST:
在 MySQL 8.0 及以后的版本中,用于表示对 JSON 数据的展开操作。
这个类型表示一个 JSON 展开操作,用于处理 JSON 数据。
1.1.3 table
表的别名
1.1.4 partitions
分区的数量:
如果查询涉及到分区表,partitions 列将显示被访问的分区数量。
如果没有使用分区或表没有被分区,则该列通常为空或显示为 NULL。
分区的选择性:
当查询只访问了一个或几个分区时,这通常表明分区策略有助于提高查询性能,因为它减少了需要扫描的数据量。
如果查询访问了所有分区,则可能意味着分区策略没有有效地限制查询范围。
分区的具体名称:
在某些情况下,partitions 列还可能显示具体的分区名称,这有助于诊断哪些分区被访问了。
1.1.5 type
system > const、eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery / index_subquery > range > index > all
system:
当表只有一行数据时,MySQL 会使用 system 类型。
这种类型比 const 更高效,因为MySQL 可以直接返回这一行数据。
const:
当查询能够唯一匹配一行数据时(例如,使用主键或唯一索引),MySQL 会使用 const 类型。
这种类型通常非常快,因为只需要读取一次数据。
eq_ref:
类似ref,区别在于使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配。
type=eq_ref
也是一种索引访问类型,但它通常出现在连接操作中,特别是当连接条件包含一个表上的主键或唯一索引时。
两个表departments
和employees
,其中employees
有一个外键dept_id
指向departments
的主键id
。如果执行如下查询:
1SELECT * FROM departments d JOIN employees e ON d.id = e.dept_id;
这里d.id
是主键,并且e.dept_id
引用了这个主键。在这种情况下,MySQL会选择eq_ref
作为访问类型。
ref:
使用非唯一索引或唯一索引的前缀扫描,返回匹配某个单独值的记录行
type=ref
是一种索引访问类型,它表示MySQL可以使用索引来查找行。当查询条件包含常量并且使用了非唯一索引或最左前缀时,通常会看到这种类型的访问方法。
fulltext:
当查询使用全文索引时,MySQL 会使用 fulltext 类型。
这种类型用于全文搜索查询。
ref_or_null:
类似于 ref,但当查询条件中有 IS NULL 或 IS NOT NULL 时,MySQL 会使用 ref_or_null 类型。
这种类型可能需要额外的全表扫描来处理 NULL 值。
index_merge:
当查询使用了多个索引,并且 MySQL 决定合并这些索引来获取结果时,MySQL 会使用 index_merge 类型。
这种类型可以在某些情况下提高性能。
unique_subquery:
当查询使用了 IN 子查询,并且子查询能够返回唯一值时,MySQL 会使用 unique_subquery 类型。
这种类型类似于 const,但用于子查询。
index_subquery:
当查询使用了 IN 子查询,并且子查询使用了索引时,MySQL 会使用 index_subquery 类型。
这种类型类似于 ref,但用于子查询。
range:
当查询使用了索引,并且条件中使用了范围操作符(如 <, <=, >, >=, BETWEEN, 或 IN)时,MySQL 会使用 range 类型。
这种类型用于索引范围扫描。
index:
索引的全扫描,MySQL遍历整个索引来查询匹配行,并不会扫描表。
ALL:
当查询没有使用索引,而是进行了全表扫描时,MySQL 会使用 ALL 类型。
这种类型通常效率较低,尤其是在处理大数据量时。
NULL:
当 EXPLAIN 无法确定访问类型时,MySQL 会显示 NULL。
这种情况通常发生在子查询或某些复杂的查询中。
1.1.6 possible_keys
possible_keys列列出的是可能被用来优化查询的索引列表。
这个列表可能包括多个索引,但MySQL查询优化器会选择最适合当前查询的一个或者多个索引来使用。
1.1.7 key
这一列显示了MySQL决定用于执行查询的索引名称。如果MySQL选择了多个索引来执行查询,则可能会列出多个索引名。如果没有使用索引,这里将显示NULL
。如果使用了表上的主键,通常会看到PRIMARY
。如果查询使用了覆盖索引(即查询所需的全部数据都包含在索引中,不需要额外的表访问),那么这个字段也会列出相应的索引。
1.1.8 key_len
这一列显示了MySQL在索引中实际使用的部分长度(以字节为单位)。这对于确定MySQL是否利用了索引的所有部分很有帮助。例如,如果你有一个多列索引(如 INDEX(a, b, c)
),并且查询只使用了前两列(a
和 b
),那么 key_len
可能只会显示前两列的总字节长度。这有助于优化查询性能,因为较小的索引长度意味着更少的数据需要被读取。
key_len
的计算方式取决于索引类型以及索引列的数据类型。下面是一些常见数据类型及其对应的字节数:
- 整型(INT):通常占用 4 字节。
- 长整型(BIGINT):通常占用 8 字节。
- 短整型(SMALLINT):通常占用 2 字节。
- 微小整型(TINYINT):通常占用 1 字节。
- 浮点型(FLOAT):通常占用 4 字节。
- 双精度浮点型(DOUBLE):通常占用 8 字节。
- 日期类型(DATE):通常占用 3 字节。
- 时间类型(TIME):通常占用 3 字节。
- 日期时间类型(DATETIME):通常占用 8 字节。
- 时间戳类型(TIMESTAMP):通常占用 4 字节。
- 字符串类型(VARCHAR, CHAR) :取决于字符集编码,例如:
- 单字节编码(如ASCII):每个字符占用 1 字节。
- 双字节编码(如UTF-16):每个字符占用 2 字节。
- 多字节编码(如UTF-8):每个字符占用 1 至 4 字节。
对于可变长度的字符串类型(如 VARCHAR
),key_len
还会包括长度前缀,通常为 1 或 2 字节,具体取决于最大长度。
1.1.9 ref
ref列显示了查询优化器如何使用索引来查找行。这里的"ref"是指引用,它通常包含用于访问表的数据的列信息。
列名:如果ref列显示为某个列的名字(例如,"col_name"),这意味着MySQL正在使用等值比较来查找匹配的行,比如在一个索引上进行查找。
const:表示该列的值是一个常量,可能是因为连接条件中使用了一个常数。
func:表示该列的值是通过函数计算得出的。
null:表示没有使用索引或者无法确定使用的索引,或者MySQL优化器决定全表扫描比使用索引更有效率。
1.1.10 rows
rows列估计了为了执行查询需要检查的行数。这个数值是由MySQL的查询优化器基于统计信息估算出来的,而不是实际检查的行数。
1.1.11 filtered
filtered指的是在应用某个条件后,预计能够过滤掉多少行数据。
100%:表示没有额外的筛选条件,或者筛选条件对行数影响不大。
小于100%:表示有额外的筛选条件被应用,例如`WHERE`子句中的条件,这些条件可以进一步减少返回的行数。比如filtered为50.00%,这意味着MySQL预计会通过附加条件过滤掉大约一半的行。
1.1.12 Extra
Using Where
表示进行了回表查询
Using filesort
"Using filesort" 表示MySQL需要进行额外的排序操作来完成查询。这意味着MySQL不能直接从索引中获取有序的结果,而是需要创建一个临时文件来对结果进行排序。这种情况通常发生在以下几种场景中:
- 没有合适的索引:当查询需要对结果进行排序,但没有适当的索引支持排序字段时。
- 部分排序:当查询使用了索引,但不是所有排序字段都在同一个索引中,或者排序字段在索引中的顺序与查询要求的顺序不同。
- 非索引排序:当查询中包含了非索引字段的排序条件。
Using temporary
"Using temporary" 表示MySQL需要创建一个临时表来存储中间结果。这种情况通常发生在以下几种场景中:
- 分组操作 :当查询中包含
GROUP BY
语句,并且分组字段没有被索引覆盖时。 - 去重操作 :当查询中包含
DISTINCT
关键字,并且相关字段没有合适的索引时。 - 复杂的子查询或连接:当查询非常复杂,MySQL无法仅通过索引优化来避免使用临时表时。
Using index(覆盖索引)
当查询只需要从索引中获取数据而不需要访问实际的数据行时,就会发生这种情况。覆盖索引是指索引包含了查询所需的所有列,因此MySQL可以仅使用索引树中的信息来完成查询,而无需再回表查找行数据。这通常会提高性能,尤其是在索引较小且表很大的情况下。
例如,如果你有一个索引 idx(a, b)
并且查询是 SELECT a, b FROM table WHERE a = 1
,那么MySQL可以直接从索引中获取 a
和 b
的值,因为它们都在索引中。
Using index condition(索引下推)
"Using index condition"通常被称为索引下推(Index Condition Pushdown,ICP)。这种优化技术允许MySQL将部分WHERE子句条件直接推送到存储引擎层进行处理,而不是像以前那样先由存储引擎返回所有可能符合条件的记录给服务器层,再由服务器层进行过滤。
索引下推的主要目的是减少服务器层需要处理的数据量,从而提高查询效率。具体来说,它使得MySQL能够在读取索引条目的时候就进行条件过滤,避免了不必要的数据传输,特别是在处理大型表时能够显著提升性能。
在一个包含复合索引的表上执行查询时,如果WHERE子句中的条件涉及到该索引的前缀字段,MySQL就可以利用索引下推技术在存储引擎层直接对索引进行筛选,从而减少需要返回给服务器层的数据量。
例如,假设有一个复合索引 idx(a, b)
,并且查询是 SELECT * FROM table WHERE a = 1 AND b = 2
。虽然索引不能完全覆盖查询(因为需要其他列),但是MySQL仍然可以利用这个索引来首先过滤掉那些 a
不等于 1
的记录,然后再进一步检查 b
的值是否等于 2
。这意味着即使不是覆盖索引,也可以在索引扫描期间排除一些不必要的行。
Using join buffer
Using join buffer (Block Nested Loop) 意味着 MySQL 使用了一种特定类型的连接算法来执行查询。
Using join buffer:这表示 MySQL 正在使用一个连接缓冲区来进行连接操作。
Block Nested Loop:这是一种连接算法,通常用于处理半连接(Semi-Join)或外部连接(Outer Join),特别是当连接条件中的一侧没有索引或者索引不可用时。
这种连接算法的工作方式是通过将较大的表分成块(block),然后对每个块进行嵌套循环连接(Nested Loop Join)。这种方法可以减少内存使用,并且可以在处理大表时更高效地利用 I/O 操作。
如果你看到 Using join buffer (Block Nested Loop),可能意味着你的查询性能可以进一步优化。例如,可以通过添加适当的索引来提高连接效率,或者调整查询逻辑以避免使用这种连接方法。如果可能的话,尽量确保参与连接的表都有有效的索引,尤其是对于连接条件中的列。
MRR(Multi-Range Read)
MRR(Multi-Range Read)是MySQL的一种优化技术,主要用于提高通过索引访问非连续记录时的效率。它的核心原理在于减少磁盘I/O操作次数,特别是在处理包含多个不连续范围的查询时。MRR的底层原理:
-
多范围读取:
- 在没有MRR的情况下,如果一个查询需要根据索引访问多个不连续的数据页,MySQL可能会逐一地对每个索引项对应的主键进行查找并读取数据行。这种逐个读取的方式会导致大量的随机I/O操作,因为每次读取都可能位于磁盘的不同位置。
- MRR则是在获取到多个索引范围后,先收集所有需要读取的主键值,并对这些主键值进行排序,然后按照排序后的顺序进行读取。这样做可以使得读取操作更加接近于顺序I/O,从而减少磁盘的寻道时间,提高I/O效率。
-
排序与合并:
- 当MySQL确定需要读取多个不连续的记录时,它首先会构建一个包含所有需要访问的主键列表。
- 这个列表会被排序,排序依据通常是物理位置(即数据页在存储介质上的位置),这样可以确保数据能够尽可能地按顺序读取。
- 排序之后,MySQL会按照这个顺序访问磁盘,尽可能地减少磁头移动的距离。
-
优化器决策:
- MySQL的查询优化器会根据查询的具体情况决定是否使用MRR。例如,如果查询涉及的范围很小或者已经是顺序的,那么MRR可能不会被启用。
- 使用MRR与否取决于优化器的成本模型,该模型会评估使用MRR相对于其他策略的优劣。
-
InnoDB存储引擎支持:
- MRR在InnoDB存储引擎中特别有用,因为InnoDB支持将多个范围请求合并成一个批量请求,这进一步减少了I/O操作次数。
- InnoDB会使用一个内部结构(如优先队列)来管理这些请求,并尝试以最有效的方式读取数据。
总之,MRR的主要目标是通过减少随机I/O操作的数量来提高数据库查询的性能。它通过排序和合并需要读取的记录地址,使读取过程尽可能接近于顺序读取,从而减少磁盘寻道时间。这是MySQL提高查询效率的一个重要机制。
No matching row in index-only scan:
在进行索引仅扫描时,没有找到匹配的行。这意味着虽然索引被用来定位行,但最终没有行满足所有条件。
Select limits evaluated:
表示查询中存在 `LIMIT` 子句,并且在查询优化阶段就已经考虑到这个限制条件。
Using intersect(N,M,...)
当使用 `IN` 子句或 `OR` 条件时,MySQL 使用多个索引的交集来找到匹配的行。
Using union(N,M,...)
类似于 `Using intersect`,但在这种情况下,MySQL 使用多个索引的并集来找到匹配的行。
Using sort_union(N,M,...)
当 `UNION` 结果需要排序时,MySQL 使用一种特殊的排序方法。
Using unique subquery
表示 MySQL 使用了一个子查询的结果作为唯一的值。
Using unique subquery (const table)
类似于 `Using unique subquery`,但是子查询的表是一个常量表。
Using where and range optimization
表示 MySQL 对 `WHERE` 子句进行了优化,使用了索引范围查找。
Using where and index condition
类似于 `Using where` 和 `Using index condition` 的组合,表明 MySQL 使用了索引条件推导来进一步优化 `WHERE` 子句。
Using index merge with (N,M,...)
MySQL 合并了多个索引的结果来找到满足条件的行。
Using index merge with (N,M,...) after filter
类似于 `Using index merge`,但在合并索引结果后还应用了额外的过滤条件。
Using index merge with (N,M,...) after sort
类似于 `Using index merge`,但在合并索引结果后还进行了排序。
Using index merge with (N,M,...) after temporary
类似于 `Using index merge`,但在合并索引结果后还创建了临时表。
Using index merge with (N,M,...) after unique sort
类似于 `Using index merge`,但在合并索引结果后还进行了唯一排序。
Using index merge with (N,M,...) after unique temporary
类似于 `Using index merge`,但在合并索引结果后还创建了唯一临时表。
Impossible WHERE noticed before reading const tables
表示 MySQL 在读取常量表之前就发现 WHERE 子句中的条件是不可能满足的,因此不会执行后续的操作。
Using where with index skip scan
表示 MySQL 使用了索引跳过扫描技术,即跳过索引中某些部分以提高效率。
Using where with index skip scan on (N,M,...)
类似于 `Using where with index skip scan`,但指定了具体的索引。
Using where with index skip scan on (N,M,...) after filter
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了过滤条件。
Using where with index skip scan on (N,M,...) after sort
类似于 `Using where with index skip scan`,但在索引跳过扫描后还进行了排序。
Using where with index skip scan on (N,M,...) after temporary
类似于 `Using where with index skip scan`,但在索引跳过扫描后还创建了临时表。
Using where with index skip scan on (N,M,...) after unique sort
类似于 `Using where with index skip scan`,但在索引跳过扫描后还进行了唯一排序。
Using where with index skip scan on (N,M,...) after unique temporary
类似于 `Using where with index skip scan`,但在索引跳过扫描后还创建了唯一临时表。
Using where with index skip scan on (N,M,...) after index condition
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了索引条件。
Using where with index skip scan on (N,M,...) after index condition and filter
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了索引条件和过滤条件。
Using where with index skip scan on (N,M,...) after index condition and sort
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了索引条件和排序。
Using where with index skip scan on (N,M,...) after index condition and temporary
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了索引条件并创建了临时表。
Using where with index skip scan on (N,M,...) after index condition and unique sort
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了索引条件和唯一排序。
Using where with index skip scan on (N,M,...) after index condition and unique temporary
类似于 `Using where with index skip scan`,但在索引跳过扫描后还应用了索引条件并创建了唯一临时表。
1.2 优化手段
使用索引
日期/时间字段索引:确保你的表中涉及日期和时间的列已经被正确地索引。
组合索引:如果查询经常涉及多个字段(比如按日期和用户ID查询),考虑创建一个包含所有相关字段的组合索引。
使用子查询
对于业务上有复杂查询要求的,可以将一条复杂的SQL语句拆分为多条子查询语句,以此来达到使用索引的效果。
选择合适的数据类型
对于日期和时间数据,使用DATE、DATETIME或TIMESTAMP等类型,而不是VARCHAR或TEXT,因为前者通常更快且更节省空间。
避免函数调用
尽量避免在WHERE子句中对日期和时间字段使用函数,如YEAR()、MONTH()等,因为这可能会导致索引失效。
如果需要使用函数,尝试预先计算这些值并存储在一个单独的列中,并对该列建立索引。
限制结果集
使用LIMIT来限制返回的结果数量。
只选择你需要的列,避免使用SELECT *。
使用分区
如果表非常大并且包含大量的历史数据,考虑使用分区。可以按照时间进行分区,比如按月份或年份分区。
分区可以将大表分解为多个小表,从而加速查询。对于跨越多个分区的查询,MySQL会只扫描相关的分区,而不是整个表。
使用覆盖索引
如果可能,设计索引使得MySQL可以从索引中直接获取所有必要的数据,而不需要回表查询。减少访问磁盘的次数,提高查询速度。
当查询所需的所有列都包含在索引中时,MySQL可以直接从索引中获取所有需要的数据,而无需访问表中的实际行。这种情况下,即使没有明确指定Using index
,也可能看到"Using index",因为它确实是在使用索引来覆盖整个查询的需求。
有时候,"Using index"可能仅仅表示MySQL正在使用索引来查找数据,但是还需要回到表中去获取额外的列数据。这种情况下,虽然也使用了索引,但它并不是一个覆盖索引。
因此,"Using index"通常指的是MySQL正在使用索引,但是否是覆盖索引取决于查询是否只需要索引中的列。如果查询条件涉及到索引中的列,并且查询选择的结果集也完全包含在这个索引中,那么它就是覆盖索引的一个例子。
调整查询逻辑
如果可能,将复杂的时间范围查询拆分为多个较简单的查询。
优化服务器配置
根据实际负载调整MySQL服务器配置参数,如innodb_buffer_pool_size等。
使用缓存
对于频繁查询但不经常变化的数据,可以考虑使用缓存机制,比如使用Redis或其他缓存技术
分片查询
检查是否可以将查询的时间范围缩小到更合理的大小。有时候,业务需求允许你只查看最近的数据,或者你可以通过其他方式过滤数据。
如果时间范围不能缩小,考虑是否可以将数据分为不同的时间段,并分别查询。
强制使用索引
在某些情况下,可以使用索引提示来强制MySQL使用某个索引,尽管这通常是最后的选择。
使用FORCE INDEX或USE INDEX提示来指定索引。
优化服务器配置
调整MySQL服务器配置参数,如innodb_buffer_pool_size等,以适应更大的数据集。
1.3 索引失效场景
-
函数计算:
- 如果在 WHERE 子句或其他条件中对索引列进行了函数操作,MySQL 无法直接利用索引,因为索引存储的是原始数据,而不是函数处理后的结果。
-
时间范围过大:
- 如果查询的时间范围非常大,导致返回的行数过多,MySQL 可能会认为全表扫描比使用索引更有效。
-
索引很少被使用或只被用于很少的查询:
- 如果一个索引很少被使用,MySQL 可能会认为维护这个索引的成本高于其带来的好处,从而在某些情况下不使用该索引。此外,这种索引可能会对其他索引的性能造成负面影响。
-
索引建的不合理影响高效索引的使用
- 如果一个索引的选择性很低(即有很多重复的值),那么当执行查询时,这个索引可能需要返回大量的行,使得查询优化器倾向于选择全表扫描而非索引扫描。
-
新的索引改变了查询优化器的行为:
- 添加新索引后,MySQL 的查询优化器可能会重新评估现有的执行计划。有时候,新的索引可能会导致原本高效的索引不再被使用。例如,添加了一个新的覆盖索引后,原本的主键索引可能不再被优化器选择。
-
使用了 OR 操作符:
- 当 WHERE 子句中包含 OR 操作符,并且两侧的操作数涉及不同的索引时,MySQL 可能会选择全表扫描而不是使用索引。
-
索引列上的函数调用:
- 如前所述,如果查询条件中对索引列进行了函数操作,MySQL 无法直接利用索引。
-
隐式类型转换:
- 如果查询条件中的数据类型与索引列的数据类型不同,可能会发生类型转换,从而导致索引失效。
-
使用 LIKE 操作符:
- 如果 LIKE 模式以通配符开头(如
%abc
),MySQL 无法有效使用索引进行全文搜索。只有当通配符出现在模式的末尾时,索引才可能被使用。
- 如果 LIKE 模式以通配符开头(如
-
索引选择性差:
- 如果索引的选择性不好(即索引列包含大量重复值),MySQL 可能会选择其他更有效的执行计划。
-
未使用索引的第一个字段:
- 如果一个复合索引包含多个字段(例如
INDEX(a, b, c)
),查询条件必须按照索引定义的顺序使用字段,否则 MySQL 可能不会使用该索引。例如,WHERE c = ? AND a = ?
将不会使用这个索引。
- 如果一个复合索引包含多个字段(例如
-
查询条件中使用变量:
- 如果查询条件中的值是由变量提供的,而这些变量本身是通过计算或其他函数得到的结果,那么 MySQL 可能无法确定如何使用索引。
-
隐式的全表扫描:
- 即使有索引存在,如果查询返回的行数过多(比如超过表大小的一定比例),MySQL 可能会认为全表扫描更加高效。
-
索引统计信息过时:
- 如果表数据经常发生变化,但是没有及时更新索引统计信息,MySQL 优化器可能会基于过时的信息做出错误的决策,从而不使用索引。
-
使用了 GROUP BY 或 DISTINCT:
- 如果 GROUP BY 或 DISTINCT 后面的列没有索引,或者索引不适合排序,MySQL 可能会选择不使用索引。
-
使用了 LIMIT 但没有合适的索引:
- 如果查询使用了 LIMIT 但没有使用索引来限制结果集的大小,MySQL 可能需要先找到所有符合条件的记录,然后再取出前几条记录,这可能导致索引失效。
-
使用了子查询:
- 复杂的子查询可能会使得 MySQL 难以决定是否使用索引,特别是在子查询内部也涉及复杂的逻辑时。
-
JOIN 顺序不当:
- 如果 JOIN 操作的顺序不当,MySQL 可能会选择次优的执行计划,从而导致索引未被充分利用。
-
使用了 NOT IN 或者 NOT EXISTS:
- 这些操作通常会导致全表扫描,因为它们需要检查所有可能的情况来确认不存在某项。
-
表锁或事务隔离级别:
- 在某些情况下,由于并发控制或事务隔离级别的要求,MySQL 可能会选择不同的执行计划,导致索引不被使用。
-
索引选择性问题:
- 索引的选择性是指索引能够区分不同记录的能力。如果一个索引的选择性较差(即很多行具有相同的索引值),MySQL 可能会选择全表扫描而非索引扫描。
-
索引合并:
- MySQL 的查询优化器有时可以合并多个索引来完成一个查询。但如果索引之间不能很好地配合,优化器可能会选择不使用任何索引。
-
覆盖索引(Covering Indexes)的缺失:
- 覆盖索引是指包含了查询所需的所有字段的索引,这样 MySQL 就不需要回到主键索引去获取额外的数据。如果查询所需的字段不在索引中,MySQL 就需要进行回表操作,这可能会导致索引部分失效。
-
查询优化器的决策:
- MySQL 使用成本模型来评估不同执行计划的成本,如果优化器认为某个执行计划的成本更低,它可能会选择不使用索引,即使索引存在。
-
索引的 B+ 树特性:
- MySQL 的索引通常是基于 B+ 树结构构建的。如果查询条件不符合 B+ 树的访问模式,例如查询范围过大或过小,可能会导致索引访问效率低下。
-
内存限制:
- MySQL 使用缓冲池来缓存索引页。如果内存资源紧张,可能会导致频繁的页置换,影响索引的使用效率。
-
并发控制机制:
- 在高并发环境下,MySQL 可能会因为锁定机制(如行锁或表锁)的影响,而选择不使用索引。
-
索引统计信息:
- MySQL 依靠索引统计信息来估计索引的使用效果。如果统计信息过时或不准确,优化器可能会做出错误的决策。
-
表结构和数据分布:
- 表的物理存储结构和数据分布可能会影响索引的使用效率。例如,如果数据分布不均匀,索引可能无法有效地减少搜索空间。
-
查询优化器的配置参数:
- MySQL 的查询优化器有一些配置参数,如
optimizer_switch
,用于控制优化器的行为。如果这些参数设置不当,可能会影响索引的使用。
- MySQL 的查询优化器有一些配置参数,如
-
硬件和系统资源:
- 硬件性能(如 CPU、磁盘 I/O)和操作系统层面的资源分配也可能影响索引的使用效果。
-
索引碎片:
- 索引随着数据的增删改查可能会产生碎片,导致索引结构不紧凑,影响查询性能。
-
索引的维护:
- 索引需要定期维护,包括重建、优化等操作,以保持其高效性。如果索引长期得不到维护,其性能可能会下降。
-
B+树的访问模式:
- B+树是一种平衡多路搜索树,用于存储索引。如果查询条件不匹配 B+树的访问模式(例如,使用了非等值比较或范围查询),MySQL 可能无法有效地利用索引。
-
索引前缀问题:
- 对于长字段(如 VARCHAR 或 TEXT 类型),创建前缀索引时,如果前缀长度不足以区分大多数记录,索引的选择性就会降低,从而导致索引失效。
-
索引选择算法:
- MySQL 使用成本模型来决定是否使用索引。如果算法估算不准确,可能导致索引选择错误。例如,如果索引统计信息不准确,优化器可能低估或高估索引的使用成本。
-
查询优化器的内部机制:
- MySQL 的查询优化器有多种策略来评估和选择最佳执行计划。如果优化器选择了次优的策略,可能导致索引不被使用。例如,优化器可能错误地估计了索引扫描和全表扫描的成本。
-
索引统计信息的准确性:
- 索引统计信息(如行数、选择性等)对于优化器的决策至关重要。如果统计信息过时或不准确,可能导致索引被误用或不用。
-
并发控制的影响:
- 在高并发环境中,MySQL 可能需要执行更多的锁定操作。如果锁定机制影响到索引的访问效率,可能会导致索引失效。
-
索引的维护和重建:
- 索引需要定期维护,包括重建和优化。如果索引长期没有维护,可能会产生碎片,影响索引的访问速度。
-
索引和表的存储引擎特性:
- 不同的存储引擎有不同的索引实现方式。例如,InnoDB 存储引擎支持聚簇索引,而 MyISAM 则不支持。如果存储引擎的特性限制了索引的有效性,可能会导致索引失效。
-
索引的隐藏属性:
- MySQL 索引内部可能有一些隐藏属性(如行指针),如果查询条件涉及这些隐藏属性,可能会导致索引无法正常工作。
-
索引的内部结构变化:
- 在数据频繁修改的情况下,索引的内部结构可能会发生变化。如果索引变得过于复杂或不平衡,可能会影响查询性能。
-
内存限制和缓存机制:
- MySQL 缓存机制(如 InnoDB 缓冲池)对于索引的访问效率有很大影响。如果内存资源不足,缓存命中率降低,可能会影响索引的使用效果。
-
硬件性能瓶颈:
- 硬件性能(如 CPU 计算能力、磁盘 I/O 速度等)也会影响索引的访问速度。如果硬件性能不足,可能导致索引访问效率低下。
-
操作系统层面的资源管理:
- 操作系统的调度和资源管理策略也可能影响 MySQL 的性能。例如,如果 CPU 调度不合理,可能会影响索引的访问速度。
-
查询优化器的配置参数:
- MySQL 的查询优化器有一些高级配置参数(如
optimizer_switch
),用于控制优化器的行为。如果这些参数设置不当,可能会影响索引的使用。
- MySQL 的查询优化器有一些高级配置参数(如
-
数据分布不均:
- 如果表中的数据分布不均匀(如存在热点数据),可能导致索引访问效率低下,尤其是在进行范围查询时。
1.4 优化案例
索引干扰导致索引失效
多个索引干扰导致索引失效如何解决_哔哩哔哩_bilibili
时间范围过大导致索引失效