如何使用"分组依据"和"在哪里"加快"选择计数(*)"?

ZA.*_*ZA. 23 mysql indexing performance group-by count

如何加快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  
Run Code Online (Sandbox Code Playgroud)

relation_title,object_title是varchar. 其中relation_title ='XXXX',返回超过1,000,000行,导致object_title上的索引 无法正常工作.

Jus*_*ant 50

为了增加难度,我会尝试以下几件事:

(更简单) - 确保您拥有正确的覆盖指数

CREATE INDEX ix_temp ON relations (relation_title, object_title);
Run Code Online (Sandbox Code Playgroud)

这应该在给定现有模式的情况下最大化perf,因为(除非你的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);
Run Code Online (Sandbox Code Playgroud)

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

此外,如果relation_title具有较小的varchar大小但对象标题具有较长的大小,则您可以潜在地仅对object_title进行哈希并在其上创建索引(relation_title, object_title_hash).

请注意,此解决方案仅在这些字段中的一个或两个相对于散列的大小非常长时才有用.

另请注意,哈希有一些有趣的区分大小写/排序规则影响,因为小写字符串的哈希值与大写字符串的哈希值不同.因此,您需要确保在对字符串进行哈希处理之前对字符串应用规范化 - 换句话说,如果您在不区分大小写的数据库中,则只使用哈希小写.您还可能希望从开头或结尾修剪空格,具体取决于数据库处理前导/尾随空格的方式.


che*_*rdo 9

使用复合索引首先尝试对GROUP BY子句中的列建立索引.可以仅使用索引数据来回答诸如此类的查询,从而完全不需要扫描表.由于索引中的记录已排序,因此DBMS不需要在组处理过程中执行单独的排序.但是,索引会降低对表的更新速度,因此如果您的表遇到大量更新,请对此谨慎.

如果将InnoDB用于表存储,则表的行将按主键索引进行物理聚类.如果它(或其中的前导部分)碰巧与您的GROUP BY键匹配,那么应该加快诸如此类的查询,因为将一起检索相关记录.同样,这避免了必须执行单独的排序.

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

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

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


Sor*_*anu 7

如果您有InnoDB,count(*)和任何其他聚合函数将执行表扫描.我在这里看到一些解决方案:

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