2011-07-13 247 views
3

我有一个简单的MyISAM表,类似于以下内容(为便于阅读而修剪 - 实际上有更多列,所有列都是恒定宽度,其中一些可以为空):MySQL:优化COUNT(*)和GROUP BY

CREATE TABLE IF NOT EXISTS `history` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
    `time` int(11) NOT NULL, 
    `event` int(11) NOT NULL, 
    `source` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `event` (`event`), 
    KEY `time` (`time`), 
); 

目前表格中只包含大约600万行(其中目前约16下方匹配查询),但是这预计将增加。给定一个特定的事件ID并按来源分组,我想知道在特定的时间间隔内有多少个具有该ID的事件被记录。对于查询的答案可能是沿着“今天,事件X对于源A发生120次,对于源B发生105次,对于源C发生900次”。

我编制的查询确实执行了这个任务,但它执行起来很糟糕,在时间跨度设置为“所有时间”时需要花费一分多钟才能执行,而在一周之内只需要超过30秒钟:

SELECT COUNT(*) AS count FROM history 
WHERE event=2000 AND time >= 0 AND time < 1310563644 
GROUP BY source 
ORDER BY count DESC 

这不是实时使用,所以即使查询需要一两秒钟,这将是罚款,但几分钟没有。在解释查询给出以下,其中最困扰我的原因很明显:

id select_type  table type possible_keys key  key_len  ref  rows Extra 
1 SIMPLE   history ref  event,time  event 4   const 160399 Using where; Using temporary; Using filesort 

我曾与各种多列索引(如(活动时间))试验,但没有改善。这似乎是一个这样的常见用例,我无法想象没有合理的解决方案,但我的谷歌搜索都归结为我已有的查询版本,没有关于如何避免临时(即使是,为什么表现如此糟糕)。

有什么建议吗?

回答

0

你说你已经尝试了多列索引。你是否还尝试过单列索引,每列一列?

UPDATE:另外,在GROUP BY子句COUNT(*)操作可能是快了很多,如果分组列也有它的索引。当然,这取决于实际上是NULL值的数量在该列中,这些列未被编入索引。

event,MySQL能够执行UNIQUE SCAN,这是相当快的,而对于time,一个RANGE SCAN将被应用,这是没有那么快......如果你单独的索引,我期望比多更好的性能 - 列的。

另外,也许你可以通过一些预期值分区表格收获/值范围:

http://dev.mysql.com/doc/refman/5.5/en/partitioning-overview.html

+0

正如您可以从顶部架构看到的,除了我尝试的多列索引之外,事件和时间都是分别索引的。 – pjohansson

+0

对不起,我错过了。我对使用'KEY'关键字指定'INDEX'的语法并不熟悉......如何将'INDEX'添加到'source'? –

+0

责备phpmyadmin的导出功能 - 我也不习惯。 :)此外,对源代码进行索引在我的测试中没有提供额外的好处。 – pjohansson

0

我给你试试这个多列索引:

ALTER TABLE `history` ADD INDEX `history_index` (`event` ASC, `time` ASC, `source` ASC); 

然后,如果没有帮助,请尝试强制在此查询中使用索引:

SELECT COUNT(*) AS count FROM history USE INDEX (history_index) 
WHERE event=2000 AND time >= 0 AND time < 1310563644 
GROUP BY source 
ORDER BY count DESC 
+0

这个特定的索引是我在玩多列索引时尝试的。强制使用索引似乎不会影响性能。 – pjohansson

+0

@pjohansson你能告诉我一个这个查询的解释吗? – Karolis

0

如果来源是已知的或者您想要查找特定来​​源的计数,那么您可以尝试这样。

select count(source ='A'or NULL)as A,count(source ='B'or NULL)as B from history; 和订购你可以在你的应用程序代码。还可以尝试将索引事件和来源一起使用。

这肯定比旧的更快。

+0

有数百种不同的来源,我需要在同一个查询中为他们提供数据。 – pjohansson

+0

你可以指定no。的行仅与时间标准和事件标准单独匹配。我的意思是从时间> 0和时间<1310563644的历史记录中选择计数(*),从历史记录中选择计数(*),其中事件= 2000 – leftrright

+0

您可以添加'show variables like'%table%'的结果吗? '并显示状态,如'%tmp%';在执行刷新状态和您的查询后。 – leftrright