【问题标题】:MySQL not using index when it's available?MySQL在可用时不使用索引?
【发布时间】:2020-10-25 00:53:44
【问题描述】:

这里是 MySQL 菜鸟。

我正在尝试将以下语句运行到sakila database

EXPLAIN SELECT * FROM actor as a
INNER JOIN film_actor as fa on a.actor_id = fa.actor_id
INNER JOIN film AS f ON fa.film_id = f.film_id;

输出是

id| select_type| table | partitions | type | possible_keys |             key          | key_len |        ref         |          rows        | filtered |   Extra
'1',   'SIMPLE',   'a',    NULL,       'ALL',  'PRIMARY',                  NULL,          NULL,            NULL,                  '200',         '100.00',    NULL
'1',   'SIMPLE',   'fa',   NULL,       'ref',  'PRIMARY,idx_fk_film_id',  'PRIMARY',     '2',        'sakila.a.actor_id',          '27',         '100.00',    NULL
'1',   'SIMPLE',   'f',    NULL,       'eq_ref','PRIMARY',                'PRIMARY',     '2',        'sakila.fa.film_id',           '1',         '100.00',    NULL

actor 表包含 actor_id 作为 PK。

film_actor 表包含actor_idfilm_id 作为复合主键加上idx_fk_film_id 作为film_id 属性的索引。

film 表包含 film_id 作为 PK。

当我查看查询计划时,我注意到type 列下有ALL actor 表,这意味着全表扫描,有谁知道为什么MySQL 没有使用@987654336 上的索引@ 寻找? MySQL 是否按输出顺序执行查询从第一行到最后一行(虽然ids 都是1)?

【问题讨论】:

  • 这些表有多大?请注意,MySQL 可能会选择在足够小的表上不使用任何索引。
  • @TimBiegeleisen actor 非常小:只有 200 行。
  • 这可能是当时没有使用索引的原因。
  • @TimBiegeleisen 在输出中,为什么ref 列下的sakila.a.actor_id 与表fafilm_actor 表)在同一行,而不是actorsakila.a.actor_id 应该在 actor 表中建立索引。我不明白。这是否意味着film_actor_table 正在使用actor 表的索引?
  • sakila.actor_idsakila.actor.actor_id 的缩写。这只是引用此连接列的 database.table.column。

标签: mysql sql indexing


【解决方案1】:

索引(尤其是非聚集索引)在速度方面有两个主要优势

  • 它们可以包含您的数据子集(例如,仅选定的列)。当查询只需要这些列而不需要其他列时,它可以从索引中读取数据。这些被称为“覆盖索引”
  • 当您过滤数据(例如,通过 WHERE 子句、非常有选择性的 JOIN 等)并且索引已经正确排序时,它们可以用于 SEEK 而不是完整的 SCAN

覆盖索引

我暂时假设您已经为actor(即actor_id)设置了一个相对标准的PK,它也创建了聚集索引。如果不是,它将是heap,但基本上意味着存储将未排序。

出于所有实际目的,聚集索引还包括表中的所有其他列(例如,它具有 actor_id、actor_first_name、actor_surname 等)。但是,它是根据定义为聚集索引(actor_id)的字段进行排序的。

如果您设置了附加(非聚集)索引,它通常是列的子集(例如,actor_surname),以在按这些字段进行搜索/排序时提供帮助。通常不会将表的所有字段都包含在其中一个字段中。

当您执行SELECT * 时,在某些时候它需要返回表/聚集索引来获取数据——这意味着您没有覆盖索引。它不能只从另一个非聚集索引中获取数据。

如果您在 Actor_Id 和(例如)Actor_Name 上有一个非聚集索引,并且您只是在执行 SELECT Actor_ID, Actor_Name FROM ...,那么它可以使用该索引作为覆盖索引(但请注意 - 如果它以它找到的方式排序没有帮助,虽然聚集的排序正确,但它可能只是使用聚集索引)。

寻求帮助过滤行

当查询优化器计算出获取数据需要做什么时,它会估计需要读取的行数(基数估计)。

即使排序正确/etc,也有两种选择

  • 我是否确定需要读取哪些行,然后逐一读取它们?或
  • 我是否只是读取整个表格并在内存中对其进行排序?

第一个称为嵌套循环连接

如果它估计返回表(例如)10 次以获取行而不是一次读取所有行的工作量更大,它将一次读取所有行。

这也是(如 cmets 中所建议的)为什么当表很小时,它只读取所有行。它可能假设做出决定所需的额外工作不值得做 - 只需阅读整个表格。


有一个关于索引的很棒的视频 - 我在这里提到了很多,因为我学到了很多关于它们的知识。它是关于 SQL Server 的,尽管这个问题对于大多数数据库来说是相当基础的。它是 Brent Ozar 的 How to think like the SQL Server Engine,它使用来自 Stack Overflow 的用户数据和声誉作为示例。

【讨论】:

  • SQL Server 和 MySQL 处理索引的方式不同;小心点。
猜你喜欢
  • 1970-01-01
  • 2015-01-28
  • 2022-09-27
  • 1970-01-01
  • 1970-01-01
  • 2018-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多