我试图从每天约2M新行的表中获取汇总结果(总的唯一IP)。
表格:
CREATE TABLE `clicks` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`hash` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`type` enum('popunder','gallery','exit','direct') COLLATE utf8_unicode_ci NOT NULL,
`impression_time` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`source_user_id` int(11) NOT NULL,
`destination_user_id` int(11) NOT NULL,
`destination_campaign_id` int(11) NOT NULL,
`destination_campaign_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`destination_campaign_url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`ip` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`referrer` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`country_code` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`country_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`country` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`isp` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`category_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`category` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`bid` float(8,2) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `ip` (`ip`),
KEY `source_user_id` (`source_user_id`),
KEY `destination_user_id` (`destination_user_id`),
KEY `destination_campaign_id` (`destination_campaign_id`),
KEY `clicks_hash_index` (`hash`),
KEY `clicks_created_at_index` (`created_at`),
KEY `campaign_date` (`destination_campaign_id`,`created_at`),
KEY `source_user_date` (`source_user_id`,`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=301539660 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
我的查询:
SELECT SUM(ips_by_date.count) as count, ips_by_date.date as date
FROM (SELECT count(DISTINCT ip) as count, DATE(created_at) as date
FROM clicks as clicks
WHERE created_at BETWEEN '2016-05-22 00:00:00' AND '2016-05-23 23:59:59'
GROUP BY DATE(created_at)) as ips_by_date
GROUP BY date;
现在,这个查询花了93秒才运行了一天,我觉得我错过了一些东西。
我是否可以进行任何优化以加快此简单计数的性能?
谢谢。
答案 0 :(得分:2)
首先,我不明白为什么子查询是必要的。内部查询每个日期有一行。无需再次聚合。其次,你的查询是两天,但我得到了关于性能的观点。
所以,让我们从:
开始SELECT count(DISTINCT ip) as count, DATE(created_at) as date
FROM clicks
WHERE created_at BETWEEN '2016-05-22 00:00:00' AND '2016-05-23 23:59:59'
GROUP BY DATE(created_at);
对于此查询,您需要clicks(created_at, ip)
上的索引。另请注意,我会将其写为:
SELECT count(DISTINCT ip) as count, DATE(created_at) as date
FROM clicks
WHERE created_at >= '2016-05-22' AND created_at < '2016-05-24'
GROUP BY DATE(created_at);
这应该会有一些改进,但我认为它不会更好,因为外部聚合仍然需要文件排序。
答案 1 :(得分:0)
这里的性能可以归结为索引的效率,因为代码中没有太大的变化空间(请参阅Gordons代码以获得更简洁的代码版本)。
(created_at)
或(created_at, ip)
上的索引不会直接给你distinct ip
而不进一步排序(因为你不按created_at
分组),但是后者至少不需要直接访问表。因此,下一次优化需要(date(created_at), ip)
上的索引,即使这意味着会有一些重复的数据。
从mysql 5.7.6开始,您可以使用生成的列创建列dt as date(created_at)
,在5.7.6之前,只需创建一列dt
并手动更新(如果您更改了您的create_at
- 值,您必须添加触发器以相应地更新该列。您的初始更新可能需要一段时间,因此请批量更新或考虑将其用于将来的查询。
添加索引(dt, ip)
现在应该为您提供单个索引/范围扫描并且没有filesort的结果,而无需从datetime计算date():
select count(distinct ip) as count, dt
from clicks
where dt >= '2016-05-22' and dt < '2016-05-24'
group by dt;
如果一切正常,即使是几百万行也只需要几秒钟。
有些事情仍然可能会给您带来麻烦:由于90秒对于200万行来说仍然是一个相对较大的数字,因此可能表明您遇到缓冲区大小/ ram / hdd问题。如果它需要你,例如80秒将拒绝并将索引加载到内存中,之后没有多少索引可以做。一个简单的测试:运行您的查询两次。如果第二次(实际上)显着更快(例如&lt;&lt;&lt; 1/10),那么您可能不得不考虑调整系统设置,体系结构或分区。话虽如此,你不应该调整你的系统(有时甚至不添加另一个索引或日期列)来进行这样的查询,并且可能减慢其他更重要的事情 - 获取每日统计数据,你可以轻松地运行任务在午夜,你可以想到所有的统计数据,并保存结果,让你在早上好好轻松地查看,如果你的查询运行需要几个小时也没关系。
答案 2 :(得分:0)
首先添加已经提到的复合索引。然后真正的性能问题是读取数十亿行来计算COUNT(DISTINCT...)
。该操作需要收集所有值,排序并执行GROUP BY
,或者尝试将所有不同的值保留在RAM中。
摘要表非常适合加速数据仓库应用程序中的SUM
,COUNT
甚至AVG
。但是COUNT(DISTINCT...)
(又名&#34;统计唯一用户&#34;)不适用于摘要表。如果你愿意接受一个小错误,那就有办法了。请参阅my blog。
您可能没有意识到这一点,但在VARCHAR
有时中全面使用255会导致不必要的性能问题。在这种情况下,您有ip
在任何tmp表中占用765个字节,可能在相关查询中。将其更改为VARCHAR(39) CHARACTER SET ascii
会将其缩短20倍! (很难预测会加快查询的速度,如果有的话。你可以通过一个简单的存储函数将其降低到BINARY(16)
。