使用JOIN的MySQL查询不使用INDEX

时间:2016-12-02 13:54:59

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

我在MySQL(简体)中有以下两个表。

  • clicks(InnoDB)
    • 包含大约70,000,000条记录
    • date_added
    • 上有索引
    • link_id引用links
    • 中的记录
  • links(MyISAM)
    • 包含的记录少得多,约为65,000

我正在尝试使用这些表运行一些分析查询。我需要提取一些数据,关于在两个指定日期内发生的点击,同时使用其他表格将其他用户选择的过滤器应用到链接表中。

我的问题围绕着索引的使用。当我运行以下查询时:

SELECT
    COUNT(1)
FROM
    clicks
WHERE
    date_added >= '2016-11-01 00:00:00'
AND date_added <= '2016-11-03 23:59:59';

我在1.40秒内收到回复。使用EXPLAIN我发现MySQL按预期使用date_added列上的索引。

EXPLAIN SELECT COUNT(1) FROM clicks WHERE date_added >= '2016-11-01 00:00:00' AND date_added <= '2016-11-16 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 | 1559288 | Using where; Using index |
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+

但是,当我在LEFT JOIN表中links时,我发现查询执行的时间要长得多:

SELECT
    COUNT(1) AS clicks
FROM
    clicks AS c
LEFT JOIN links AS l ON l.id = c.link_id
WHERE
    c.date_added >= '2016-11-01 00:00:00'
AND c.date_added <= '2016-11-16 23:59:59';

以6.50秒完成。使用EXPLAIN我发现date_added列上未使用该索引:

EXPLAIN SELECT COUNT(1) AS clicks FROM clicks AS c LEFT JOIN links AS l ON l.id = c.link_id WHERE c.date_added >= '2016-11-01 00:00:00' AND c.date_added <= '2016-11-16 23:59:59';
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+
| id | select_type | table | type   | possible_keys | key        | key_len | ref           | rows    | Extra       |
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+
|  1 | SIMPLE      | c     | range  | date_added    | date_added | 4       | NULL          | 6613278 | Using where |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY       | PRIMARY    | 4       | c.link_id     |       1 | Using index |
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+

正如您所看到的,索引未用于较大表中的date_added列,并且似乎需要更长时间。当我加入其他表时,这似乎变得更糟。

有谁知道为什么会发生这种情况,或者我有什么办法可以让它使用点击表中date_added列的索引?

修改

我刚尝试使用其他方法从数据库中获取统计信息。我的方法的第一步涉及从点击表中提取一组不同的link_id。我发现我在这里再次看到同样的问题,没有加入。索引未被使用:

我的查询:

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_id值。知道为什么会这样吗?我的link_id索引以及date_added

2 个答案:

答案 0 :(得分:1)

不完全确定,但考虑将条件从WHERE条件移至JOIN ON条件,因为您正在执行外连接(LEFT JOIN),与inner join不同,它会带来性能差异<{1}}或where子句中的条件是等效的。

join on

答案 1 :(得分:1)

您是否想使用普通JOIN代替LEFT JOINLEFT JOIN保留右侧的所有行,因此它将产生与未连接表相同的COUNT()值。如果您只想计算右侧表中左侧表中具有匹配行的行,请使用JOIN,而不是LEFT JOIN

尝试删除date_added上的索引并将其替换为(date_added, link_id)上的复合索引。 This sort of index is called a covering inde X。当查询规划器知道它可以从索引中获取所需的一切时,它不必退回到表中。在这种情况下,查询计划程序可以随机访问索引到日期范围的开头,然后执行index range scan到范围的结尾。不过,它仍然需要引用另一张表。

编辑)为了实验,请尝试更窄的日期范围。查看EXPLAIN是否发生了变化。在这种情况下,查询计划程序可能会猜测您的date_added列的基数错误。

您可以尝试index hint。例如,尝试

SELECT COUNT(1) AS clicks
  FROM clicks AS c USE INDEX (date_added)
  LEFT JOIN links AS l ON l.id = c.link_id
 WHERE etc

但是,从您的EXPLAIN输出判断,您已经在date_added上进行了范围扫描。无论你喜欢与否,你的下一步是复合覆盖指数。

确保links(id)上有索引。可能有,因为它可能是PK。

尝试使用COUNT(*)代替COUNT(1)。它可能不会有所作为,但它值得一试。 COUNT(*)只计算行而不是为每个行计算一些东西。

(Nitpick)你的日期范围闻起来很有趣。使用<作为范围的结尾,以获得最佳效果,例如。

 WHERE c.date_added >= '2016-11-01'
   AND c.date_added <  '2016-11-17';

编辑:看,MySQL查询规划器使用了很多关于表格结构的内部知识。并且,截至2016年底,每个表只能使用一个索引来满足查询。这是一个限制。

SELECT DISTINCT column实际上是一个相当复杂的查询,因为它必须对有问题的column进行重复数据删除。如果该列上有索引,则查询计划程序可能会使用它。选择该索引意味着它无法选择其他索引。

复合索引(覆盖索引)有时但并不总是解决这种索引选择困境,并允许索引双重使用。您可以在http://use-the-index-luke.com/

了解所有这些内容

但是如果您的操作限制阻止添加复合索引,则您需要使用一秒查询。这不是那么糟糕。

当然,说你不能添加复合索引来完成你的工作是这样的:

A :东西从高速公路上的卡车上掉下来。

B :在这些东西上面放一个防水布并系好它。

A :我的老板不让我把卡车放在卡车上。

B :好吧,那么,开车很慢。