我正在处理一个不断增长的表格,目前包含大约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)和/或分发不同数据的策略感兴趣服务器等
答案 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