尽管有索引,但使用GROUP BY查询的DISTINCT COUNT太慢了

时间:2018-05-25 08:51:33

标签: mysql query-optimization

我有以下查询,计算每周每个区域的血管数量:

SELECT zone, 
    DATE_FORMAT(creation_date, '%Y%u') AS date, 
    COUNT(DISTINCT vessel_imo) AS vessel_count 
  FROM vessel_position
  WHERE zone IS NOT NULL
   AND creation_date >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
  GROUP BY zone, date;

该表有大约4000万行。执行计划是:

+----+-------------+-----------------+------------+-------+--------------------+------+---------+------+----------+----------+------------------------------------------+
| id | select_type | table           | partitions | type  | possible_keys      | key  | key_len | ref  | rows     | filtered | Extra                                    |
+----+-------------+-----------------+------------+-------+--------------------+------+---------+------+----------+----------+------------------------------------------+
|  1 | SIMPLE      | vessel_position | NULL       | range | creation_date,zone | zone | 5       | NULL | 21190904 |    50.00 | Using where; Using index; Using filesort |
+----+-------------+-----------------+------------+-------+--------------------+------+---------+------+----------+----------+------------------------------------------+

每个索引的vessel_imozonecreation_date列。主键是复合键(vessel_imocreation_date)。

当我查看查询配置文件时,我可以看到我花费了大量时间Creating sort index

我有什么办法可以进一步改进这个查询吗?

3 个答案:

答案 0 :(得分:1)

假设数据一旦插入,就不会改变,然后构建并维护一个汇总表。

该表将有三列:区域,周,以及该周的计数。在每周开始时,仅构建 前一周的行(每个区域一行;跳过NULL)。然后构建一个查询来对付该表 - 它将非常快,因为它将获取更少的行。

同时,INDEX(creation_date, zone, vessel_imo)作为辅助索引,将使每周任务合理有效(约为当前查询的52倍)。

答案 1 :(得分:0)

这取决于您的过滤条件的选择程度以及您的表格结构。过滤条件是否选择20%的行,5%,1%,0.1%?

如果您的答案小于5%,则以下索引可能有所帮助:

create index ix1_date_zone on vessel_position (creation_date, zone);

如果您的表有多列和/或重列,则此选项可能仍然很慢,具体取决于您的过滤条件的选择性。

否则,您可以尝试使用更多昂贵的索引,以避免使用该表并执行:

create index ix2_date_zone_imo on vessel_position 
  (creation_date, zone, vessel_imo);

此索引的维护成本更高 - 阅读insertupdatedelete行 - 但select会更快。

尝试这两种选择,并根据您的需求选择最佳选择。

答案 2 :(得分:0)

SET @mystartdate = DATE_SUB(CURDATE(),INTERVAL 12 MONTH);

SELECT区域,DATE_FORMAT(creation_date,'%Y%u')AS日期,        COUNT(DISTINCT vessel_imo)AS vessel_count     来自vessel_position     在哪里creation_date> = @mystartdate       AND区> 0     GROUP BY区域,日期;

可以在更短的时间内提供结果,请发布每次(旧的和建议的)第二次运行的比较时间

请发布新的EXPLAIN SELECT ...以确认现在使用的创建日期索引。

除非允许更改旧数据,为什么你必须收集12个月的历史记录,超过1个月前的数字不会改变。