26

如何加快select count(*)速度group by
它太慢了,而且使用频率很高。
我在使用超过 3,000,000 行的表时select count(*)遇到了很大的麻烦。group by

select object_title,count(*) as hot_num   
from  relations 
where relation_title='XXXX'   
group by object_title  

关系标题,对象标题是 varchar。 其中,relation_title='XXXX'返回超过 1,000,000 行,导致object_title上的索引 无法正常工作。

4

9 回答 9

52

以下是我会尝试的几件事,按照难度增加的顺序:

(更容易) - 确保你有正确的覆盖指数

CREATE INDEX ix_temp ON relations (relation_title, object_title);

鉴于您现有的模式,这应该最大化性能,因为(除非您的 mySQL 优化器版本真的很愚蠢!)它将最小化满足您的查询所需的 I/O 数量(不像索引是整个索引的相反顺序必须扫描),它将覆盖查询,因此您不必触摸聚集索引。

(有点难) - 确保你的 varchar 字段尽可能小

MySQL 上 varchar 索引的性能挑战之一是,在处理查询时,字段的完整声明大小将被拉入 RAM。因此,如果您有一个 varchar(256) 但仅使用 4 个字符,则在处理查询时您仍需支付 256 字节的 RAM 使用量。哎哟! 因此,如果您可以轻松缩小 varchar 限制,这应该会加快您的查询速度。

(更难) - 标准化

30% 的行只有一个字符串值,这显然是为了规范化到另一个表中,这样您就不会重复字符串数百万次。考虑规范化为三个表并使用整数 ID 将它们连接起来。

在某些情况下,您可以在幕后进行规范化并使用与当前表名称匹配的视图隐藏规范化......然后您只需让您的 INSERT/UPDATE/DELETE 查询知道规范化,但可以让您的 SELECT 单独.

(最难) - 散列你的字符串列并索引散列

如果规范化意味着更改太多代码,但您可以稍微更改架构,您可能需要考虑为字符串列创建 128 位哈希(使用MD5 函数)。在这种情况下(与规范化不同),您不必更改所有查询,只需更改 INSERT 和一些 SELECT。无论如何,你会想要散列你的字符串字段,然后在散列上创建一个索引,例如

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

请注意,您需要使用 SELECT 以确保您是通过哈希索引进行计算而不是拉入聚集索引(需要解析 object_title 的实际文本值才能满足查询)。

此外,如果relation_title 具有较小的varchar 大小但对象标题具有较长的大小,那么您可能仅对object_title 进行散列并在上创建索引(relation_title, object_title_hash)

请注意,此解决方案仅在这些字段中的一个或两个相对于哈希大小非常长时才有帮助。

另请注意,散列对区分大小写/排序规则有有趣的影响,因为小写字符串的散列与大写字符串的散列不同。因此,您需要确保在对字符串进行散列之前对其应用规范化 - 换句话说,如果您在不区分大小写的数据库中,则仅对小写进行散列。您可能还想从头或尾修剪空格,具体取决于您的数据库如何处理前导/尾随空格。

于 2009-10-12T19:09:37.630 回答
10

首先尝试使用复合索引为 GROUP BY 子句中的列建立索引。像这样的查询可能只使用索引数据来回答,根本不需要扫描表。由于索引中的记录已排序,因此 DBMS 不需要在组处理过程中执行单独的排序。但是,索引会减慢表的更新速度,因此如果您的表经历大量更新,请务必谨慎。

如果使用 InnoDB 作为表存储,表的行将由主键索引进行物理聚集。如果那个(或它的主要部分)恰好与您的 GROUP BY 键匹配,那应该会加快这样的查询,因为相关记录将被一起检索。同样,这避免了必须执行单独的排序。

一般来说,位图索引是另一种有效的替代方案,但据我所知,MySQL 目前不支持这些。

物化视图将是另一种可能的方法,但 MySQL 也不直接支持这种方法。但是,如果您不要求 COUNT 统计信息完全是最新的,则可以定期运行CREATE TABLE ... AS SELECT ...语句来手动缓存结果。这有点难看,因为它不透明,但在您的情况下可能是可以接受的。

您还可以使用触发器维护逻辑级缓存表。该表将为您的 GROUP BY 子句中的每一列提供一个列,并带有一个 Count 列,用于存储该特定分组键值的行数。每次在基表中添加或更新一行时,针对该特定分组键在汇总表中插入或递增/递减计数器行。这可能比假物化视图方法更好,因为缓存的摘要将始终是最新的,并且每次更新都是增量完成的,并且对资源的影响应该更小。但是,我认为您必须注意缓存表上的锁争用。

于 2009-06-23T08:47:54.080 回答
7

如果你有 InnoDB,count(*) 和任何其他聚合函数都会进行表扫描。我在这里看到了一些解决方案:

  1. 使用触发器并将聚合存储在单独的表中。优点:诚信。缺点:更新慢
  2. 使用处理队列。优点:更新快。缺点:旧状态会一直持续到队列被处理,因此用户可能会觉得缺乏完整性。
  3. 完全分离存储访问层并将聚合存储在单独的表中。存储层将了解数据结构,并且可以应用增量而不是进行完整计数。例如,如果您在其中提供“addObject”功能,您将知道何时添加了对象,因此聚合会受到影响。然后你只做一个update table set count = count + 1. 优点:快速更新、完整性(您可能希望使用锁,以防多个客户端可以更改同一记录)。缺点:您将一些业务逻辑和存储结合起来。
于 2009-06-24T09:00:01.927 回答
2

我看到有几个人问您使用什么引擎进行查询。我强烈建议您使用 MyISAM,原因如下:

InnoDB - @Sorin Mocanu 正确识别出无论索引如何,您都将进行全表扫描。

MyISAM - 始终保持当前行数方便。

最后,正如@justin 所说,确保你有正确的覆盖索引:

CREATE INDEX ix_temp ON relations (relation_title, object_title);
于 2009-10-12T19:23:43.687 回答
1

测试计数(myprimaryindexcolumn)并将性能与您的计数(*)进行比较

于 2009-06-24T15:15:06.383 回答
1

你应该保留一个单独的计数表!该表可以在每次插入/删除时更新。它会使这种查询瞬间完成。

于 2020-09-29T17:48:33.747 回答
0

在某个时候,您确实需要更多 RAM/CPU/IO。您可能已经为您的硬件实现了这一目标。

我会注意到,对于命中超过表中总行数的 1-2% 的查询,使用索引(除非它们覆盖)通常是无效的。如果您的大型查询正在执行索引查找和书签查找,则可能是因为缓存计划仅来自全天查询。尝试添加 WITH (INDEX=0) 以强制进行表扫描,看看它是否更快。

取自: http ://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4-0104-47aa-b548-e8428073b6e6&cat=&lang=&cr= &sloc=&p=1

于 2009-06-23T08:28:53.150 回答
0

如果你整个表的大小是多少,你应该查询元表或信息模式(存在于我知道的每个 DBMS 上,但我不确定 MySQL)。如果您的查询是选择性的,您必须确保它有一个索引。

AFAIK 你无能为力。

于 2009-06-23T08:40:32.613 回答
0

我建议存档数据,除非有任何特定原因将其保存在数据库中,或者您可以对数据进行分区并单独运行查询。

于 2009-10-16T20:48:20.843 回答