【问题标题】:Optimize GROUP BY after ranged index query范围索引查询后优化 GROUP BY
【发布时间】:2010-12-30 00:47:11
【问题描述】:

我有一个内容应用程序,它需要在一个时间片内计算响应,然后按响应数对它们进行排序。它目前适用于小型数据集,但需要扩展到数百万行。我当前的查询不起作用。

mysql> describe Responses;
+---------------+---------------------+------+-----+---------+-------+
| Field         | Type                | Null | Key | Default | Extra |
+---------------+---------------------+------+-----+---------+-------+
| site_id       | int(10) unsigned    | NO   | MUL | NULL    |       |
| content_id    | bigint(20) unsigned | NO   | PRI | NULL    |       |
| response_id   | bigint(20) unsigned | NO   | PRI | NULL    |       |
| date          | int(10) unsigned    | NO   |     | NULL    |       |
+---------------+---------------------+------+-----+---------+-------+

表类型是InnoDB,主键是on (content_id, response_id)。 (content_id, date) 上有一个附加索引,用于查找对一段内容的响应,而在我遇到问题的查询中使用的 (site_id, date) 上有另一个附加索引:

mysql> explain SELECT content_id id, COUNT(response_id) num_responses
               FROM Responses
               WHERE site_id = 1
                 AND date > 1234567890
                 AND date < 1293579867
               GROUP BY content_id
               ORDER BY num_responses DESC
               LIMIT 0, 10;
+----+-------------+-----------+-------+---------------+------+---------+------+------+-----------------------------------------------------------+
| id | select_type | table     | type  | possible_keys | key  | key_len | ref  | rows | Extra                                                     |
+----+-------------+-----------+-------+---------------+------+---------+------+------+-----------------------------------------------------------+
|  1 | SIMPLE      | Responses | range | date          | date | 8       | NULL |  102 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+-----------+-------+---------------+------+---------+------+------+-----------------------------------------------------------+

这是我能想到的最好的方法,但它最终会出现在需要计算的 1,000,000 行中,从而导致要排序的 10,000 行,拉入少数结果。

我也想不出预先计算计数的方法,因为日期范围是任意的。我可以随意更改主键:它可以按任意顺序由 content_id、response_id 和 site_id 组成,但不能包含日期。

该应用程序主要使用 PHP 开发,因此如果有更快的方法通过将查询拆分为子查询、使用临时表或在应用程序端执行操作来完成相同的结果,我愿意接受建议。

【问题讨论】:

  • 也许你可以尝试索引site_id, date plus content_id
  • 在 InnoDB 中,二级索引通过指向主键来工作(然后使用主键检索结果)。这样做的副作用是所有辅助键本质上都在末尾附加了主键列。因此索引 (site_id, date) 的行为类似于 (site_id, date, content_id, response_id)。

标签: sql mysql optimization group-by


【解决方案1】:

(根据要求从 cmets 转发)

设置一个包含三列的表:id、date 和 num_responses。 num_responses 列包含给定日期对给定 ID 的响应数。适当地回填表格,然后在每晚午夜(或更晚)左右运行一个脚本,为前一天添加一个新行。

然后,要得到你想要的行,你可以只查询上面提到的表。

【讨论】:

    【解决方案2】:

    与其每次都计算,不如缓存上次查询后计算的计数,并通过将日期条件放入WHERE子句中添加计数的增量来更新缓存?

    【讨论】:

    • 日期范围是任意的,而且数据更新频繁,所以缓存很少会命中,如果命中的话,很可能是陈旧的。而且它仍然没有解决初始查询缓慢/密集的问题。 ://
    • 日期范围是任意的? “日期”是否真的反映了响应的时间戳?
    • SQL 查询中的日期范围是任意的。也就是说,用户可以选择任何时间片来获取统计信息。表中的日期列包含做出响应的时间。此查询的目的是获取在任意时间段(一年、一个月、3 天、2 小时,从任何给定的开始和结束日期)内对内容片段响应最多的列表。 Date 是一个日期/时间的 unix 时间戳。
    【解决方案3】:

    您是否考虑过按日期对表进行分区?桌子上有索引吗?

    【讨论】:

    • 威廉的建议也是一个绝妙的主意。实际上,很容易编写一个脚本来执行此操作,该脚本可以在每晚统计这些总数(因此,您将拥有 id、date 和 num_responses 列)。然后,查询这个较小的表以获取给定日期范围内的响应总和将是微不足道的。如果每天运行一次脚本太慢,那么您可以每小时运行一次(甚至每十分钟一次),但您可能需要某种自动递增的行 ID,以便您可以从上次停下的地方继续。
    • 另外,是否有理由将日期列的数据类型设置为无符号整数而不是日期?
    • 我将日期存储为 unix 时间戳。它速度更快,它使日期列上的索引更小,并且我在应用程序端处理所有时区详细信息(因为我缓存数据,我存储原始时间并在呈现之前根据需要进行转换)。
    • 嗯,您不必统计每两天组合的响应数,而只需统计每一天的响应数。如果这样做,您可能有 (id,date,num_responses) 三元组(每个构成此汇总表中的一行),例如,可能看起来像 (12345,20101201,2342),(12345,20101202,4349 ),(23456,20101201,345346)等让我们将此表称为 num_responses_by_id(下文继续)。
    • 然后,要获取给定日期范围内每个 id 的响应数,您可以简单地执行 select id, sum(num_responses) as num_responses from num_responses_by_id where date>=[whatever] and date
    猜你喜欢
    • 2011-08-29
    • 2012-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 2020-08-23
    相关资源
    最近更新 更多