【问题标题】:How to speed up "select count(*)" with "group by" and "where"?如何使用“group by”和“where”加速“select count(*)”?
【发布时间】:2009-06-23 08:25:12
【问题描述】:

如何用group by加速select count(*)
太慢了,用的很频繁。
我在使用 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_titleobject_title 是 varchar。 where relation_title='XXXX',返回超过 1,000,000 行,导致 object_title 上的索引无法正常工作。

【问题讨论】:

  • 您能否提供更多详细信息,例如。整个Select和表结构?另一个第一枪:您是否正确使用索引?
  • 我在下面添加了一些潜在的解决方案,但我同意 Kosi 的观点,即查看表定义(尤其是 varchar 列的长度!)和索引定义对诊断这一点非常有帮助。
  • 关系是 Innodb 还是 MyISAM 表?
  • ZA - 为您的 object_title 和 relation_title 列定义的最大长度是多少?看看我下面的答案,为什么这很重要......
  • EXPLAIN 是您的朋友,将帮助您调整索引:dev.mysql.com/doc/refman/5.0/en/explain.html>

标签: mysql performance indexing count group-by


【解决方案1】:

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

(更简单) - 确保您拥有正确的覆盖索引

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 查询知道规范化但可以离开单独的选择。

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

如果规范化意味着更改太多代码,但您可以稍微更改架构,您可能需要考虑为字符串列创建 128 位哈希(使用 MD5 function)。在这种情况下(与规范化不同),您不必更改所有查询,只需更改 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) 上创建索引。

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

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

【讨论】:

  • Justin 在这里提到的覆盖索引绝对是从这个查询中获得良好性能的最佳方式。
  • 谢谢,很有用
  • CHAR 字段是固定长度的,VARCHAR 是可变长度字段。这意味着存储要求是不同的 - 无论您存储什么,CHAR 始终占用相同数量的空间,而 VARCHAR 的存储要求因存储的特定字符串而异。因此,使 Varchar 字段尽可能小不会对性能产生太大影响。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 较小的列可能会有所帮助:如果表扫描是不可避免的,那么较小的表将花费更少的时间来扫描。也许您可以发布表结构和一些示例数据以及确切的查询。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    InnoDB - @Sorin Mocanu 正确确定您将执行全表扫描,而不管索引如何。

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

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

    CREATE INDEX ix_temp ON relations (relation_title, object_title);
    

    【讨论】:

    • 仅供参考,MyISAM 对 COUNT(*) 查询的巨大速度优势仅适用于计算整个表中的行数时。如果有 WHERE 子句,那么 MyISAM 和 InnoDB 都通过计算索引中的行数来计算计数。请参阅mysqlperformanceblog.com/2006/12/01/count-for-innodb-tables 了解更多信息。
    【解决方案5】:

    测试 计数(我的主索引列) 并将性能与您的计数进行比较(*)

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        有一个点是你真正需要的 更多 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

        【讨论】:

        • 我以为这是 MS SQL 开始的,但张贴者添加了 mysql 标签...
        • 请注意,问题标记为“mysql”而不是“mssql”。
        • 是的,'mysql'。我尝试“强制索引(主)”让 mysql 本身不使用索引。有效,20s 到 15s。
        【解决方案8】:

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

        AFAIK 你无能为力了。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            猜你喜欢
            • 2020-07-10
            • 1970-01-01
            • 2020-08-15
            • 1970-01-01
            • 2012-10-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多