具有数百万条记录的表上的实时聚合

时间:2015-07-09 09:41:51

标签: mysql sql aggregation

我正在处理一个不断增长的表格,目前包含大约500万条记录。每天增加约100000条新记录。

该表包含有关广告系列的信息,并在与另一个表的查询中加入:

CREATE TABLE `statistics` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `ip_range_id` int(11) DEFAULT NULL,
    `campaign_id` int(11) DEFAULT NULL,
    `payout` decimal(5,2) DEFAULT NULL,
    `is_converted` tinyint(1) unsigned NOT NULL DEFAULT '0',
    `converted` datetime DEFAULT NULL,
    `created` datetime DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `created` (`created`),
    KEY `converted` (`converted`),
    KEY `campaign_id` (`campaign_id`),
    KEY `ip_range_id` (`ip_range_id`),
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

另一个表包含IP范围:

CREATE TABLE `ip_ranges` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `ip_range` varchar(11) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `ip_range` (`ip_range`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

聚合查询如下:

SELECT
    SUM(`payout`) AS `revenue`, 
    (SELECT COUNT(*) FROM `statistics` WHERE `ip_range_id` = `IpRange`.`id`) AS `clicks`, 
    (SELECT COUNT(*) FROM `statistics` WHERE `ip_range_id` = `IpRange`.`id` AND `is_converted` = 1) AS `conversions` 
FROM `ip_ranges` AS `IpRange` 
INNER JOIN `statistics` AS `Statistic` ON `IpRange`.`id` = `Statistic`.`ip_range_id`
GROUP BY `IpRange`.`id` 
ORDER BY `clicks` DESC 
LIMIT 20

查询大约需要20秒才能完成。

这是EXPLAIN返回的内容:

id  select_type         table       type   possible_keys    key          key_len  ref               rows    Extra

1   PRIMARY             ip_range    index  PRIMARY          PRIMARY      4        NULL              306552  Using index; Using temporary; Using filesort
1   PRIMARY             statistic   ref    ip_range_id      ip_range_id  5        db.ip_range.id    8       Using where
3   DEPENDENT SUBQUERY  statistics  ref    ip_range_id      ip_range_id  5        func              8       Using where
2   DEPENDENT SUBQUERY  statistics  ref    ip_range_id      ip_range_id  5        func              8       Using where; Using index

将ip_ranges表中的点击次数和转化次数作为额外列进行缓存不是一种选择,因为我还需要能够过滤campaign_id列(以及将来可能的其他列)。所以这些聚合需要有点实时。

在多维度和接近实时的大型表格上进行聚合的最佳策略是什么?

请注意,我不一定只是想让查询更好,但我也对可能涉及其他数据库系统(NoSQL)和/或分发不同数据的策略感兴趣服务器等

2 个答案:

答案 0 :(得分:2)

您的查询看起来过于复杂。无需一次又一次地查询同一个表:

select
  sum(payout) as revenue, 
  count(*) as clicks, 
  sum(s.is_converted = 1) as conversions 
from ip_ranges r
inner join statistics s on r.id = s.ip_range_id
group by r.id 
order by clicks desc 
limit 20;
编辑(接受后):关于如何处理这样的任务的实际问题:

您希望查看所有表格中的数据,并希望您的结果是最新的。然后除了读取所有数据(全表扫描)之外别无选择。如果表是宽的(即有很多列),您可能想要创建覆盖索引(即包含所有列所涉及的索引),因此不会读取表,而是读取索引。那么,还有什么?在全表扫描中,建议使用并行访问,据我所知,MySQL没有提供。所以你可能想切换到另一个DBMS。然后看看DBMS提供了什么。也许并行查询会从分区表中受益。最后想到的是硬件,即更多的CPU,更快的驱动器等。

另一种选择可能是从表中删除旧数据。假设您需要当前年度的详细信息,但只需要前几年的汇总数据。然后让另一个表old_statistics只保留所需的总和和数量,例如

table old_statistics
(
  ip_range_id,
  revenue,
  conversions
);

然后你要汇总来自统计数据的数据,这些数据会小得多,因为它只包含当前年份的数据,并添加old_statistics来获得结果。

答案 1 :(得分:0)

试试这个

SELECT
    SUM(`payout`) AS `revenue`, 
    SUM(case when `ip_range_id` = `IpRange`.`id` then 1 else 0 end) AS `clicks`, 
    SUM(case when `ip_range_id` = `IpRange`.`id` and `is_converted` = 1 then 1 else 0 end)  
      AS `conversions` 
FROM `ip_ranges` AS `IpRange` 
INNER JOIN `statistics` AS `Statistic` ON `IpRange`.`id` = `Statistic`.`ip_range_id`
GROUP BY `IpRange`.`id` 
ORDER BY `clicks` DESC 
LIMIT 20