【问题标题】:Why is my SQL query not using the table's composite index?为什么我的 SQL 查询不使用表的复合索引?
【发布时间】:2020-02-20 23:21:29
【问题描述】:

我有一个 users 表,其中包含以下列:id(主键)、typeexternal_idexternal_typecreated_atupdated_at

索引:

  • 主要(id)
  • 独特的(external_id, external_type, type)
  • 非唯一(updated_at)

还有一个设置表,其中包含以下列:iduser_idnamevaluecreated_atupdated_attype

索引:

  • 主要(id)
  • 独特的(user_id, name)
  • 非唯一(user_id)
  • 非唯一(updated_at)

我执行查询:

SELECT users.id, users.type, users.external_id, users.created_at, users.updated_at,

  settings.id, settings.settings_id, settings.name, settings.value, 
  settings.created_at, settings.updated_at, settings.type

FROM users
  
  LEFT OUTER JOIN settings on settings.user_id = users.id

WHERE users.external_id=3 and users.external_type=“Owner”

在解释报告中,我看到:

  • 对于 users 表,(external_id, external_type, type) 索引被标识为可能的键,但没有使用
  • 设置表使用 (user_id, name) 索引

目标

  • 我想优化这个查询
  • 所以我想让 users 表使用 (external_id, external_type, type) 复合索引

我为调试所做的事情:

  • 如果我更改 SELECT 语句的第一行以删除 users.created_at、users.updated_at,它使用索引
  • 如果我尝试向 users 表添加 (external_id, external_type) 非唯一索引,它仍然不使用它
  • 如果我将查询的 WHERE 子句更改为添加且 users.type=“Blah”,它将使用索引

我错过了什么?

【问题讨论】:

  • 您的查询和表定义不一致。该查询的外部字段来自settings 而不是users
  • 什么版本的 MySQL?
  • 请提供SHOW CREATE TABLE;可能存在整理或其他问题。

标签: mysql sql indexing query-optimization database-indexes


【解决方案1】:

避免双重查找

您的索引是(external_id, external_type, type),但为了获取查询所需的所有信息,它必须使用该索引来查找行,然后使用自动包含在该索引末尾的id从主表中查找 created_atupdated_at 列。

优化器判断直接进入主表会更简单,因此忽略索引。

您可以通过您的陈述看到这一事实的证据:

如果我将 SELECT 语句的第一行更改为删除 users.created_at,users.updated_at,它使用索引

删除这些列后,它不再需要进行双重查找来完成查询。索引中的单一查找是让它选择使用该索引的原因。

如下:

如果我将查询的 WHERE 子句更改为添加且 users.type=“Blah”,它 使用索引

我猜优化器现在认为双重查找是值得的,如果它可以通过这种更具选择性的查询来减少足够多的行。理解优化器的推理并不总是那么容易,但这似乎是最明显的原因。

解决方案

要让它使用索引,你只需要使它不需要通过使其成为覆盖索引来执行双重查找。

(external_id,  external_type, type, created_at, updated_at)

该索引将允许它避免双重查找,因为它可以过滤第一列,然后只需使用索引中的其余列来满足该表的 SELECT 要求,而无需跳回主表。

【讨论】:

  • 这是一个很好的答案 - 谢谢你解释得如此透彻! :)
【解决方案2】:

这回答了问题的原始版本。

使用LEFT JOIN 然后在WHERE 子句中过滤,您可能会混淆优化器。

首先将查询编写为:

SELECT u.id, u.type, u.external_id, u.created_at, u.updated_at,
       s.id, s.settings_id, s.name, s.value, 
       s.created_at, s.updated_at, s.type
FROM users u JOIN
     settings s
     ON s.user_id = u.id
WHERE s.external_id = 3 and s.external_type = 'Owner'

表别名只是让查询更容易读写,不会影响性能。

然后,您需要以下索引:

  • settings(external_id, external_type, user_id)
  • user(id)

MySQL 应该使用settings 索引来查找与external_idexternal_type 匹配的用户,只需在索引中查找即可。然后它将使用user_idusers 表中查找相应的信息。这应该是最快的方法。

实际上,您可以免费获得第二个,因为它是主键。我不费心创建覆盖索引,因为您选择了这么多列。但这可能会提供稍微更好的性能。

【讨论】:

  • 抱歉,我在试图匿名化某些公司数据的描述中弄错了查询 - 已更新!!
【解决方案3】:

不确定你使用的是什么版本的mysql。 8.0之前,mysql innodb是不持久化统计的,如果你的数据有倾斜,内存中的统计很难代表数据。在您的情况下,查询优化器可能认为表扫描是最快的,如果统计信息表明表 users 中的大部分数据为 external_id = 3 和 external_type = 'Owner',因为表上没有索引覆盖被选择的列,并且如果使用索引,查询引擎需要根据索引对数据进行查找。

当您更改为 SELECT 索引中的唯一列时,索引将成为覆盖索引,查询引擎将不需要进行查找。

【讨论】:

  • 当你添加 users.type="Blah" 时,索引变得更有选择性,优化器决定使用这个索引。如果你的查询绝对可以从索引中受益,你添加一个查询提示来强制mysql使用索引
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-15
  • 1970-01-01
  • 1970-01-01
  • 2013-07-25
  • 2011-10-05
相关资源
最近更新 更多