在 table 上实时聚合数百万条记录

Real-time aggregation on a table with millions of records

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

table 包含有关广告活动的信息,并在查询时与另一个 table 合并:

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;

另一个 table 包含 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 秒才能完成。

这就是解释 returns:

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 table 中的点击和转化缓存为额外的列不是一个选项,因为我还需要能够过滤 campaign_id 列(并且可能将来的其他专栏)。所以这些聚合需要有点实时。

在多个维度上近乎实时地对大型 table 进行聚合的最佳策略是什么?

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

试试这个

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

您的查询看起来过于复杂。不需要一次又一次查询相同的table:

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;

编辑(接受后):关于如何处理这样的任务的实际问题:

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

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

table old_statistics
(
  ip_range_id,
  revenue,
  conversions
);

然后,您将从统计数据中汇总数据,因为它只包含当年的数据,所以它会小得多,然后添加 old_statistics 以获得结果。