我在MySQL(简体)中有以下两个表。
clicks
(InnoDB)
date_added
列link_id
引用links
表links
(MyISAM)
我正在尝试使用这些表运行一些分析查询。我需要提取一些数据,关于在两个指定日期内发生的点击,同时使用其他表格将其他用户选择的过滤器应用到链接表中。
我的问题围绕着索引的使用。当我运行以下查询时:
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
。
答案 0 :(得分:1)
不完全确定,但考虑将条件从WHERE
条件移至JOIN ON
条件,因为您正在执行外连接(LEFT JOIN
),与inner join
不同,它会带来性能差异<{1}}或where
子句中的条件是等效的。
join on
答案 1 :(得分:1)
您是否想使用普通JOIN
代替LEFT JOIN
? LEFT 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 :好吧,那么,开车很慢。