【问题标题】:MySQL index is not used when certain columns are returned返回某些列时不使用 MySQL 索引
【发布时间】:2020-03-07 09:50:30
【问题描述】:

我有一个带有 created_on 字段的表,该字段由自身编制索引。当查看一个慢查询时,我意识到根据我返回的列不使用索引。在以下两个示例中,我有 foobar 列。

  • foo 是具有其他三列的另一个索引的一部分。
  • bar 根本没有被编入索引。

EXPLAIN SELECT created_on, foo FROM T1
WHERE created_on > NOW() - INTERVAL 5 DAY;

未使用索引,搜索了 +3 百万行。 (Using where)

如果我添加一个LIMIT 子句,它使用索引。

EXPLAIN SELECT created_on, foo FROM T1
WHERE created_on > NOW() - INTERVAL 5 DAY
LIMIT 1000;

如果使用另一列 bar 它也将使用索引。

EXPLAIN SELECT created_on, bar FROM T1
WHERE created_on > NOW() - INTERVAL 5 DAY;

使用了索引,只搜索了 150 万行。 (Using where; Using index)

为什么 MySQL 会跳过这些查询之一的索引?

【问题讨论】:

  • 问题是如果一半的行符合过滤条件,为什么要使用索引。
  • @GordonLinoff 对不起,我不明白。查询之间的唯一区别是我是返回附加列A 还是B。为什么这会改变使用索引来过滤结果(WHERE 子句)?
  • 。 .索引通常用于减少正在读取的数据页数。根据您的描述,您的查询不会发生这种情况,因此在任何情况下都使用非覆盖索引令人惊讶。一个例外是聚集索引,但我希望它总是被使用。

标签: mysql sql indexing mariadb


【解决方案1】:

EXPLAIN 输出的Extra 列中的“使用索引”表示查询已通过覆盖索引 得到满足,无需在基础表中查找数据页。 p>

对于第一个查询,这意味着 MySQL 使用的索引包含查询中引用的T1 中的所有列,即foocreated_on

(如果created_on 是索引中的前导列,则没有给出指示,允许 MySQL 使用范围扫描操作来快速消除大量行,或者 MySQL 是否正在检查索引中的每个条目,进行全索引扫描。我们需要更仔细地查看EXPLAIN 输出中的其他列。)


使用为第二个查询显示的EXPLAIN(EXPLAIN 输出中没有“使用索引”),我们推断没有同时包含barcreated_on 的索引。

因此 MySQL 将不得不访问基础表中的页面,以获取其中一些列的值。

现在的问题是使用索引来缩小需要查看的行是否会更便宜(更快),或者扫描整个表是否更快。


通常,当我们说 MySQL“使用索引”时,我们指的不是覆盖索引。我们通常指的是 range 扫描操作或 ref 查找操作。我们通常说 MySQL 有效地利用了索引,显着减少了需要检查的行数或行数;通过非常有效地消除大量不需要检查的行来快速缩小所需的行。

要确定是否正在使用索引,我们查看 EXPLAIN 输出中的 keykeylenref 列。 (当 Extra 列中没有“使用索引”时,这并不意味着 MySQL 没有“使用索引”。)


更直接地回答所提出的问题:

问: 为什么 MySQL 会跳过这些查询之一的索引?

答: 当 MySQL 优化器确定有合适的索引可用时,并且当使用索引的执行计划是最低的估计成本时,MySQL 优化器将选择使用索引的执行计划。它考虑的所有计划。优化器可能会确定不使用索引的计划具有较低的估计成本。

添加合适的索引可以极大地影响 SQL 性能,因为它可以为优化器提供更高效的操作。

鉴于我们可以通过添加合适的索引来提升性能,我们对性能不佳的下意识反应是“添加索引”是可以理解的。

但添加索引并不是“灵丹妙药”。我们需要谨慎使用不会禁用 MySQL 使用索引能力的 SQL 构造,并且我们还需要仔细考虑哪些索引是最优的,不仅仅是针对单个语句,还要考虑整个 SQL 工作负载。

这个答案几乎没有触及 MySQL“使用索引”的主题。

为了完整起见,我们还应该注意(冒着不必要地弄脏水的风险)MySQL 优化器的行为受可用统计数据、一些系统变量的设置(尤其是optimizer_switch)以及SQL 文本中包含优化器“提示”。

【讨论】:

  • created_on 不是复合索引。它自己被索引。 foobar 都不在带有 created_on 的索引中。
  • MySQL 仅在使用覆盖包含查询中引用的所有列的索引时才会显示“使用索引”。
  • 问题中提供的 EXPLAIN 输出的摘录不足以让我们确定是否使用了索引,以及 使用了哪个索引,以及它是否完整扫描索引,或使用前导 created_on 列进行范围扫描操作。 (问题说,对于其中一个查询,使用了“the”索引;但问题也表明存在不止一个索引。)
【解决方案2】:
SELECT created_on, foo FROM T1
WHERE created_on > NOW() - INTERVAL 5 DAY;

优化器可能会也可能不会使用INDEX(created_on)。这取决于过去 5 天内创建的行占表的百分比。将使用一小部分(例如,低于 20%)的索引。

另一方面,INDEX(created_on, foo)(按此顺序)因为它将“覆盖”。

foo 是具有其他三列的另一个索引的一部分。

索引中列的顺序非常很重要。如需进一步讨论,请提供SHOW CREATE TABLE

如果使用另一个列栏,它也将使用索引。

该索引可能PRIMARY KEY。如果您需要进一步讨论,请提供SHOW CREATE TABLE

(使用位置;使用索引)

“使用索引”表示所有必要的列都在索引中。因此,只需要查看索引的 BTree。每个二级索引都隐含地包含 PRIMARY KEY 列。所以,我推断你有INDEX(bar) 并且 created_atPRIMARY KEY 的某个地方。

为什么 MySQL 会跳过这些查询之一的索引?

优化器会询问哪个更快(通常会给出正确答案):

  • 索引是“覆盖”,所以我们只使用索引。
  • 让我们使用索引,但在它与数据的 BTree 之间跳转以获取其他一些列。
  • 我不能用WHERE 做很多事情,但我可以使用ORDER BY 的索引;让我们这样做,并希望我不必扫描太多行。
  • 平底船。让我们忽略所有索引并简单地扫描数据,丢弃任何与WHERE 子句不匹配的行。

是的,最后一个选项通常是最快的。一些可能的特征:

  • 没有二级索引对WHERE 有很大帮助。
  • 有一个WHERE,但它不会消除很多行。
  • 没有ORDER BY,或者没有与之匹配的索引。
  • 没有LIMIT

如果使用 MySQL,EXPLAIN FORMAT=JSON SELECT ... 将提供它为做出决定所做的“基于成本”的计算。

我的cookbook 帮助构建最佳索引。

【讨论】:

  • 优秀的概要。这里提供了足够的信息来帮助理解 MySQL 优化器的作用,以及对它可用的一些操作的基本理解。 +10
猜你喜欢
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多