【问题标题】:MySQL select distinct query not using indexMySQL选择不使用索引的不同查询
【发布时间】:2016-12-05 11:30:33
【问题描述】:

我有一张桌子clicks

CREATE TABLE `clicks` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `link_id` int(11) NOT NULL,
  `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
  PRIMARY KEY (`id`),
  KEY `link_id` (`link_id`),
  KEY `date_added` (`date_added`)
) ENGINE=InnoDB AUTO_INCREMENT=90899051 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

具有以下索引:

+--------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| clicks |          0 | PRIMARY    |            1 | id          | A         |    79808649 |     NULL | NULL   |      | BTREE      |         |               |
| clicks |          1 | link_id    |            1 | link_id     | A         |      276154 |     NULL | NULL   |      | BTREE      |         |               |
| clicks |          1 | date_added |            1 | date_added  | A         |    79808649 |     NULL | NULL   |      | BTREE      |         |               |
+--------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

我正在尝试对此表运行一些分析查询,但我发现它需要很长时间才能运行。以下面的查询为例:

SELECT
    DISTINCT(link_id) AS link_id
FROM
    clicks
WHERE
    date_added >= '2016-11-01 00:00:00'
AND date_added <= '2016-12-05 10:16:00'

完成此查询几乎需要一分钟。我通过在未使用索引的查询上运行 EXPLAIN 发现。

+----+-------------+--------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows     | Extra       |
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-------------+
|  1 | SIMPLE      | clicks | index | date_added    | link_id | 4       | NULL | 79786609 | Using where |
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-------------+

我希望通过使用date_added 列上的索引来过滤结果集,然后从结果中提取不同的link_ids 来运行查询。

有谁知道为什么没有使用索引,或者我可以做些什么来强制使用它?

注意:这个问题是一个更大问题的一部分,与我上周发布的一个未解决问题密切相关 - MySQL query with JOIN not using INDEX


编辑

解释我的查询,不使用任何索引提示:

EXPLAIN SELECT DISTINCT(link_id) FROM clicks WHERE date_added >= '2016-11-01 00:00:00' AND date_added <= '2016-12-05 23:59:59';
+----+-------------+---------------------------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table                     | type  | possible_keys | key     | key_len | ref  | rows     | Extra       |
+----+-------------+---------------------------+-------+---------------+---------+---------+------+----------+-------------+
|  1 | SIMPLE      | clicks                    | index | date_added    | link_id | 4       | NULL | 79816660 | Using where |
+----+-------------+---------------------------+-------+---------------+---------+---------+------+----------+-------------+

用索引提示解释我的查询:

EXPLAIN SELECT DISTINCT(link_id) FROM clicks USE INDEX(date_added) IGNORE INDEX(link_id) WHERE date_added >= '2016-11-01 00:00:00' AND date_added <= '2016-12-05 23:59:59';
+----+-------------+---------------------------+------+---------------+------+---------+------+----------+------------------------------+
| id | select_type | table                     | type | possible_keys | key  | key_len | ref  | rows     | Extra                        |
+----+-------------+---------------------------+------+---------------+------+---------+------+----------+------------------------------+
|  1 | SIMPLE      | clicks                    | ALL  | date_added    | NULL | NULL    | NULL | 79816882 | Using where; Using temporary |
+----+-------------+---------------------------+------+---------------+------+---------+------+----------+------------------------------+

编辑 2

在我的查询中使用FORCE INDEX(date_added)(查询完成更快,12.05 秒):

EXPLAIN SELECT DISTINCT(link_id) FROM clicks FORCE INDEX(date_added) WHERE date_added >= '2016-11-01 00:00:00' AND date_added <= '2016-12-05 23:59:59';
+----+-------------+---------------------------+-------+---------------+------------+---------+------+----------+------------------------------+
| id | select_type | table                     | type  | possible_keys | key        | key_len | ref  | rows     | Extra                        |
+----+-------------+---------------------------+-------+---------------+------------+---------+------+----------+------------------------------+
|  1 | SIMPLE      | clicks                    | range | date_added    | date_added | 4       | NULL | 17277508 | Using where; Using temporary |
+----+-------------+---------------------------+-------+---------------+------------+---------+------+----------+------------------------------+

【问题讨论】:

  • 你分析过表格了吗?
  • 如果将DISTINCT(link_id) 替换为count(*),会得到什么?
  • @DuduMarkovitz EXPLAIN 语句指示“使用索引”,这是我想要的,我在大约 3.5 秒内得到结果。
  • using index 表示覆盖索引。如果您使用 distinct,则需要为此创建一个多列索引。原因:对于 count(*) mysql 不需要检索实际的字段值,对于不同的它需要。
  • 表格的百分之几包含在日期范围内? (这与您的一个问题有关。)

标签: mysql sql indexing database-performance query-performance


【解决方案1】:

首先,没有使用索引是不正确的。 explain 结果清楚地表明使用了link_id 索引。

至于为什么不使用date_added 索引,答案很简单:MySQL 决定反对,因为它认为link_id 索引会是更好的选择。

您需要了解的是,您的查询中有 2 个操作可以通过索引加速:

  1. 过滤数据范围
  2. 确保每个link_id 只返回一次。

如果您真的想优化这个查询,那么您可以在link_id, date_added 字段上创建一个多列索引。您在对 Gordon 的回答的评论中写道,这是不可能的。

因此,MySQL 必须决定使用其中一个索引来加速 2 个操作中的哪一个。它决定link_id 索引是更好的选择。如果您不同意它的决定,那么您可以使用 index hints 向 MySQL 表明您更喜欢使用(use indexforce index)或忽略(ignore index)特定索引。

只需指示 MySQL 忽略 link_id 索引并使用 date_added 索引。只需确保在调整后检查查询速度即可。

【讨论】:

  • 我假设没有使用索引,因为“Extra”列中缺少“Using index”。这是不正确的吗?我认为可能有必要在这两列上添加一个复合索引,但这意味着我目前无法承受一些重大的停机时间。我已经尝试按照建议使用索引提示 - 我没有看到我的 EXPLAIN 的输出有差异,但我看到速度略有提高,几乎减半。谢谢。
  • 解释输出的key 字段显示使用了哪个索引。额外内容将为您提供额外的信息,这些信息可以解释如何使用索引。我希望看到未更改的解释结果,以防我发现一些差异:)
  • 如何解释explain的结果请看MySQL文档:dev.mysql.com/doc/refman/5.7/en/explain-output.html
  • 哦,对了,感谢您为我澄清这一点。解释结果不变是什么意思?我不确定你是怎么知道的,但我确实稍微修改了解释的结果,因为我们的表和列的名称非常糟糕,我想在我的问题中说清楚。谢谢:)
  • 使用索引提示后是否可以看到解释结果?
【解决方案2】:

如果您有单个链接的表格,您可以尝试:

select l.link_id
from links l
where exists (select 1
              from clicks c
              where c.link_id = l.link_id and
                    c.date_added >= '2016-11-01 00:00:00' and
                    c.date_added <= '2016-12-05 10:16:00'
             );

为此,您需要在clicks(link_id, date_added) 的索引中。

【讨论】:

  • 感谢您的建议,但在我的情况下,我实际上是在运行我的查询来解决另一个问题,所以这不是一个选项。不幸的是,我在link_iddate_added 上也没有复合索引,而且我的表太大而无法考虑更改。您对为什么我的查询中没有使用索引有任何线索吗?再次感谢
猜你喜欢
  • 2011-11-24
  • 1970-01-01
  • 2011-11-09
  • 1970-01-01
  • 2013-07-10
  • 2020-09-14
  • 1970-01-01
  • 2020-10-25
相关资源
最近更新 更多