如何使用select count(*)
加快group by
的速度?
它太慢而且经常使用。
使用select count(*)
和group by
时,我遇到的问题是行表超过3,000,000行。
select object_title,count(*) as hot_num
from relations
where relation_title='XXXX'
group by object_title
relation_title , object_title 是varchar。 where relation_title ='XXXX',返回超过1,000,000行,导致 object_title 上的索引无效。
答案 0 :(得分:49)
为了增加难度,我尝试了几件事:
(更简单) - 确保您拥有正确的覆盖索引
CREATE INDEX ix_temp ON relations (relation_title, object_title);
这应该在给定现有模式的情况下最大化perf,因为(除非你的mySQL优化器版本真的很笨!)它将最小化满足查询所需的I / O数量(不像索引的顺序是相反的顺序)必须扫描整个索引)并且它将覆盖查询,因此您不必触及聚集索引。
(稍微努力一点) - 确保您的varchar字段尽可能小
MySQL上的varchar索引的一个挑战是,在处理查询时,字段的完整声明大小将被拉入RAM。因此,如果您有一个varchar(256),但只使用4个字符,那么在处理查询时,您仍然需要支付256字节的RAM使用量。哎哟!因此,如果您可以轻松缩小varchar限制,这可以加快您的查询速度。
(更难) - 规范化
30%的行具有单个字符串值,这对于规范化到另一个表是一个明确的呼声,因此您不会重复数百万次字符串。考虑规范化为三个表并使用整数ID来加入它们。
在某些情况下,您可以在封面下进行规范化,并使用与当前表的名称匹配的视图隐藏规范化...然后您只需要使INSERT / UPDATE / DELETE查询知道规范化但可以离开你的SELECT单独。
(最难) - 散列字符串列并为散列索引
如果规范化意味着更改了太多代码,但您可以稍微更改模式,则可能需要考虑为字符串列创建128位哈希(使用MD5 function)。在这种情况下(与规范化不同),您不必更改所有查询,只需更改INSERT和一些SELECT。无论如何,你需要哈希你的字符串字段,然后在哈希上创建一个索引,例如
CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);
请注意,您需要使用SELECT来确保您通过哈希索引进行计算而不是拉入聚簇索引(需要解析object_title的实际文本值以满足查询)。
另外,如果relation_title的varchar大小很小但是对象标题的长度很大,那么你可以只对hash_title进行哈希并在(relation_title, object_title_hash)
上创建索引。
请注意,此解决方案仅在这些字段中的一个或两个相对于散列的大小非常长时才有用。
另请注意,哈希有一些有趣的区分大小写/排序规则影响,因为小写字符串的哈希值与大写字符串的哈希值不同。因此,您需要确保在对字符串进行哈希处理之前对字符串应用规范化 - 换句话说,如果您在不区分大小写的数据库中,则只使用哈希小写。您还可能希望从开头或结尾修剪空格,具体取决于数据库处理前导/尾随空格的方式。
答案 1 :(得分:9)
使用复合索引首先尝试对GROUP BY子句中的列建立索引。可以仅使用索引数据来回答诸如此类的查询,从而完全不需要扫描表。由于索引中的记录已排序,因此DBMS不需要在组处理过程中执行单独的排序。但是,索引会降低对表的更新速度,因此如果您的表经历大量更新,请对此谨慎。
如果将InnoDB用于表存储,则表的行将按主键索引进行物理聚类。如果它(或其中的前导部分)碰巧与您的GROUP BY键匹配,那么应该加快诸如此类的查询,因为将一起检索相关记录。同样,这避免了必须执行单独的排序。
一般来说,位图索引是另一种有效的选择,但据我所知,MySQL目前不支持这些。
物化视图将是另一种可能的方法,但同样在MySQL中不直接支持。但是,如果您不要求COUNT统计信息完全是最新的,则可以定期运行CREATE TABLE ... AS SELECT ...
语句来手动缓存结果。这有点难看,因为它不透明,但在你的情况下可能是可以接受的。
您还可以使用触发器维护逻辑级缓存表。此表将为GROUP BY子句中的每一列提供一列,其中包含一个Count列,用于存储该特定分组键值的行数。每次在基表中添加或更新行时,在该特定分组键的摘要表中插入或递增/递减计数器行。这可能比伪造的物化视图方法更好,因为缓存的摘要将始终是最新的,并且每次更新都是以递增方式完成的,并且应该对资源的影响较小。但是,我认为你必须注意缓存表上的锁争用。
答案 2 :(得分:7)
如果您有InnoDB,count(*)和任何其他聚合函数将执行表扫描。我在这里看到一些解决方案:
update table set count = count + 1
。优点:快速更新,完整性(您可能希望使用锁定,以防多个客户端可以更改相同的记录)。缺点:你结合了一些业务逻辑和存储。答案 3 :(得分:2)
我看到有几个人问过您使用什么引擎进行查询。我强烈建议您使用MyISAM进行以下规定:
InnoDB - @Sorin Mocanu正确识别出无论索引如何都会进行全表扫描。
MyISAM - 始终保持当前行计数。
最后,正如@justin所说,确保你有正确的覆盖指数:
CREATE INDEX ix_temp ON relations (relation_title, object_title);
答案 4 :(得分:1)
测试 COUNT(myprimaryindexcolumn) 并将效果与您的计数进行比较(*)
答案 5 :(得分:0)
你真正需要的是一个点 更多RAM / CPU / IO。你可能已经为你的硬件命中了。
我会注意到使用索引通常是无效的(除非它们是 覆盖率超过表中总行数的1-2%的查询。 如果您的大型查询正在进行索引查找和书签查找,则可能是 因为缓存计划仅来自一天的总查询。尝试添加 在WITH(INDEX = 0)中强制进行表扫描并查看它是否更快。
答案 6 :(得分:0)
如果您的整个表的大小,您应该查询元表或信息模式(我知道的每个DBMS上都存在,但我不确定MySQL)。如果您的查询是选择性的,您必须确保它有一个索引。
AFAIK你无能为力。
答案 7 :(得分:0)
我建议归档数据,除非有任何特定原因将其保留在数据库中,或者您可以对数据进行分区并单独运行查询。
答案 8 :(得分:0)
您应该保留一个单独的计数表!该表可以在每次插入/删除时进行更新。它将使这种查询瞬间完成。