4

我有一张每天增长数千万行的表。表中的行包含有关页面查看流量的每小时信息。

表中的索引在 url 和 datetime 上。

我想按天而不是按小时汇总信息。我该怎么做?这是一个举例说明我正在尝试做的查询:

SELECT url, sum(pageviews), sum(int_views), sum(ext_views)
FROM news
WHERE datetime >= "2012-08-29 00:00:00" AND datetime <= "2012-08-29 23:00:00"
GROUP BY url
ORDER BY pageviews DESC
LIMIT 10;

但是,上面的查询永远不会完成。表中有数百万行。有没有更有效的方法可以获取这些汇总数据?

4

2 回答 2

6

每天几千万行是相当多的。

假设:

  • 每天只有 1000 万条新记录;
  • 您的表格仅包含您在问题中提到的列;
  • urlTEXT平均(Punycode)长度约为77 个字符的类型;
  • pageviews是类型INT
  • int_views是类型INT
  • ext_views是类型INT;和
  • datetime是类型DATETIME

那么每天的数据将占用大约 9.9 × 10 8个字节,几乎是 1GiB/天。实际上它可能要多得多,因为上述假设相当保守。

除其他事项外, MySQL 的最大表大小由其数据文件所在的底层文件系统确定。如果您在 Windows 或 Linux 上使用 MyISAM 引擎(如您在下面的评论中所建议的那样)而不进行分区,那么几个 GiB 的限制并不少见;这意味着该表将在一个工作周内达到其容量!

正如@Gordon Linoff提到的,您应该对表进行分区;但是,每个表有 1024 个分区的限制。每天 1 个分区(这在您的情况下是非常明智的),在分区开始被重用之前,您将被限制在一个表中存储 3 年以下的数据。

因此,我建议您将每年的数据保存在自己的表中,每个表都按天分区。此外,正如@Ben 解释的那样,复合索引(datetime, url)会有所帮助(我实际上建议创建一个dateDATE(datetime)并为其编制索引,因为它将使 MySQL在执行查询时能够修剪分区);并且,如果行级锁定和事务完整性对您不重要(对于此类表,它们可能不重要),使用 MyISAM 可能并不愚蠢:

CREATE TABLE news_2012 (
  INDEX (date, url(100))
)
Engine = MyISAM
PARTITION BY HASH(TO_DAYS(date)) PARTITIONS 366
SELECT *, DATE(datetime) AS date FROM news WHERE YEAR(datetime) = 2012;

CREATE TRIGGER news_2012_insert BEFORE INSERT ON news_2012 FOR EACH ROW
  SET NEW.date = DATE(NEW.datetime);

CREATE TRIGGER news_2012_update BEFORE UPDATE ON news_2012 FOR EACH ROW
  SET NEW.date = DATE(NEW.datetime);

如果您选择使用MyISAM ,您不仅可以存档已完成的年份myisampackMERGE使用,但它只对语句有用,因为视图既不可更新也不可插入):UNIONVIEWSELECTUNION

DROP TABLE news;
CREATE TABLE news (
  date DATE,
  INDEX (date, url(100))
)
Engine = MERGE
INSERT_METHOD = FIRST
UNION = (news_2012, news_2011, ...)
SELECT * FROM news_2012 WHERE FALSE;

然后,您可以在此合并表上运行上述查询(以及任何其他查询):

SELECT   url, SUM(pageviews), SUM(int_views), SUM(ext_views)
FROM     news
WHERE    date = '2012-08-29'
GROUP BY url
ORDER BY SUM(pageviews) DESC
LIMIT    10;
于 2012-08-30T22:49:38.560 回答
5

几点:

  1. 此外,作为您要过滤的唯一谓词,您可能应该将索引datetime作为第一列。
  2. 您按 订购pageviews。我会假设您想按sum(pageviews).
  3. <您查询的是 23 小时而不是 24 小时的数据。您可能希望从第二天午夜开始使用显式小于 ,以避免遗漏任何内容。
SELECT url, sum(pageviews), sum(int_views), sum(ext_views)
  FROM news
 WHERE datetime >= '2012-08-29 00:00:00'
   AND datetime < '2012-08-30 00:00:00'
 GROUP BY url
 ORDER BY sum(pageviews) DESC
 LIMIT 10;

您可以将其编入索引,datetime, url, pageviews, int_views, ext_views但我认为这太过分了;所以,如果索引不是太大datetime, url似乎是一个好方法。唯一可以确定的方法是对其进行测试并确定查询中的任何性能改进是否值得在索引维护中花费额外的时间。

正如 Gordon 在评论中刚刚提到的,您可能需要研究partitioning。这使您能够查询一个较小的“表”,它是较大的“表”的一部分。如果您的所有查询都基于日级别,那么听起来您可能需要每天创建一个新查询。

于 2012-08-30T21:09:26.660 回答