从大表中检索聚合数据的更快方法?
Posted
技术标签:
【中文标题】从大表中检索聚合数据的更快方法?【英文标题】:Faster way of retrieving aggregate data from large table? 【发布时间】:2012-08-30 20:44:26 【问题描述】:我有一个每天增长数千万行的表。表中的行包含有关页面查看流量的每小时信息。
表格上的索引是 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;
不过,上面的查询永远不会结束。表中有数百万行。有没有更有效的方法可以获取这些汇总数据?
【问题讨论】:
表上的索引是什么,查询的解释计划是什么? @Ben,索引在 url 和 datetime 上。解释计划是什么? @Ben,我将如何进行明确的字符到日期转换? datetime 列的数据类型为 datetime。 mysql 会为我们完成从字符到日期时间类型的转换吗?我以为是的。它应该选择 24 小时的数据,从 0:00 到 23:59。 这是一个相当困难的评论问题!它指示数据库优化器在执行查询时将采用的路径。 dev.mysql.com/doc/refman/5.5/en/execution-plan-information.html @egidra 。 . .您需要了解分区表。这是一个复杂的主题,需要在这里回答。您正在不必要地阅读所有历史数据。每天使用一个单独的分区,您将大大减少此类查询的负载。 【参考方案1】:数千万行每天相当多。
假设:
每天只有 1000 万条新记录; 您的表格仅包含您在问题中提到的列;url
是 TEXT
类型,平均(Punycode)长度为 ~77 characters;
pageviews
是 INT
类型;
int_views
是 INT
类型;
ext_views
是 INT
类型;和
datetime
是 DATETIME
类型
那么每天的数据将占用大约 9.9 × 108 字节,几乎是 1GiB/天。实际上可能要多得多,因为上述假设相当保守。
MySQL 的maximum table size 是由其数据文件所在的底层文件系统决定的。如果您在 Windows 或 Linux 上使用 MyISAM 引擎(如您在下面的评论中所建议的那样)而不进行分区,那么几个 GiB 的限制并不少见;这意味着该表将在一个工作周内达到其容量!
正如@Gordon Linoff 提到的,你应该partition 你的桌子;但是,每个表都有一个limit 的 1024 个分区。每天 1 个分区(这在您的情况下是非常明智的),在分区开始重用之前,您将被限制在一个表中存储 3 年以下的数据。
因此,我建议您将每年的数据保存在自己的表中,每个表都按天分区。此外,作为@Ben explained,(datetime, url)
上的复合索引会有所帮助(我实际上建议从DATE(datetime)
创建一个date
列并对其进行索引,因为它将使MySQL 在执行查询时能够prune 分区) ;并且,如果行级锁定和事务完整性对您来说并不重要(对于此类表,它们可能不重要),使用 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,您不仅可以存档已完成的年份(使用 myisampack
),还可以将您的原始表替换为 MERGE
包含所有基础年份表的 UNION
的表(一个在 InnoDB 中也可以使用的替代方法是创建一个 VIEW
,但它仅对 SELECT
语句有用,因为 UNION
视图既不可更新也不可插入):
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;
【讨论】:
我将从最明显的优化开始,在 datetime,url 上创建一个索引。当我尝试创建索引时,出现以下错误: ERROR 1071 (42000): Specified key was too long;最大密钥长度为 1000 字节 @egidra:建议您为url
指定前缀长度--例如INDEX(datetime, url(100))
,我已经用它更新了上面的答案-您应该选择的实际长度将取决于您的数据:为了将表格过滤到相对较小,需要读取 url
列的多少个字符记录数?
不多,大概 100 个字符就能将这些 url 区分开来。【参考方案2】:
几点:
-
此外,作为您要过滤的唯一谓词,您应该
可能有一个以
datetime
作为第一列的索引。
您通过pageviews
订购。我会假设您想通过sum(pageviews)
订购。
您查询的是 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 刚刚在 cmets 中提到的,您可能需要查看 partitioning。这使您能够查询一个较小的“表”,它是较大的“表”的一部分。如果您的所有查询都基于日级别,那么您可能需要每天创建一个新查询。
【讨论】:
当我尝试在 datetime, url 上创建索引时,我收到以下错误:ERROR 1071 (42000): Specified key was too long;最大密钥长度为 1000 字节 @Ben:你删除你的评论 re str_to_date() 了吗?我看不到它......只是想知道,因为我不明白你为什么提出这个建议:这些肯定是有效的datetime literals? @eggyal,是的,你是对的......我总是更喜欢明确地转换和蒙蔽自己。以上是关于从大表中检索聚合数据的更快方法?的主要内容,如果未能解决你的问题,请参考以下文章