代码之家  ›  专栏  ›  技术社区  ›  Anil Namde

count(*)真的很贵吗?

  •  18
  • Anil Namde  · 技术社区  · 14 年前

    我有一个页面,其中有4个选项卡,根据不同的表显示4个不同的报告。

    我使用 select count(*) from <table> 查询并在选项卡上显示每个表中可用的行数。因此,每个页面回发会导致5 count(*) 要执行的查询(4个用于获取计数,1个用于分页)和1个用于获取报表内容的查询。

    现在我的问题是 伯爵(*) 查询非常昂贵——我应该将行计数(至少那些显示在选项卡上的行计数)保持在页面的视图状态,而不是多次查询吗?

    count(*)查询有多贵?

    7 回复  |  直到 14 年前
        1
  •  7
  •   Ruben Bartelink    14 年前

    你需要附加 SQL Profiler an app level profiler like L2SProf 并在以下之前查看上下文中的实际查询成本:

    • 猜测问题所在并试图确定潜在解决方案的可能好处

    • 允许其他人在DA Interwebs上为您猜测-有很多错误信息没有引用,包括在这篇文章中(但不是在这篇文章中:p)

    当你做到了这一点,你就会明白什么是最好的方法,也就是说,选择计数是否支配一切,等等。

    这样做之后,你也会知道你选择做的任何改变是否产生了积极或消极的影响。

        2
  •  8
  •   Ruben Bartelink    14 年前

    总的来说 COUNT(*) 成本与满足查询条件的记录数加上准备这些记录所需的时间(这取决于底层查询的复杂性)成正比。

    在处理单个表的简单情况下,通常会进行特定的优化,以使这样的操作更便宜。例如,做 伯爵(*) 没有 WHERE 单一条件 MyISAM MySQL -这是即时的,因为它存储在元数据中。

    例如,让我们考虑两个查询:

    SELECT  COUNT(*)
    FROM    largeTableA a
    

    因为每个记录都满足查询,所以 伯爵(*) 成本与表中记录的数量成正比(即,与返回的内容成正比)(假设它需要访问行,并且没有特定的优化来处理它)

    SELECT  COUNT(*)
    FROM    largeTableA a
    JOIN    largeTableB b
    ON      a.id = b.id
    

    在这种情况下,发动机最有可能使用 HASH JOIN 执行计划如下:

    1. 在较小的表上生成哈希表
    2. 扫描较大的表,查找哈希表中的每个记录
    3. 边走边数火柴。

    在这种情况下, 计数(*) 开销(步骤3)可以忽略不计,查询时间将完全由步骤1和2定义,即构建哈希表并查找它。对于这样的查询,时间将是 O(a + b) :它实际上并不取决于匹配的数量。

    但是,如果两个目录上都有索引 a.id b.id , the MERGE JOIN 可能被选中 伯爵(*) 时间将再次与匹配数成比例,因为每次匹配后都将执行索引查找。

        3
  •  2
  •   Alex K.    14 年前

    正如其他人所说 COUNT(*) 总是在物理上计算行数,所以如果您可以这样做一次并缓存结果,那当然是更好的选择。

    如果您基准测试并确定成本可以忽略不计,则(当前)不会有问题。

    如果这对你的场景来说太贵了,你可以让你的分页变得“模糊”,如“ 显示约30000的1到500 “通过使用

    SELECT rows FROM sysindexes WHERE id = OBJECT_ID('sometable') AND indid < 2

    它将返回一个 近似值 行的数量(大约是因为直到检查点才更新)。

        4
  •  1
  •   Jon Seigel    14 年前

    如果页面变慢,可以考虑的一件事是尽可能减少数据库往返次数。即使你 COUNT(*) 查询是o(1),如果你做的足够多,肯定会减慢速度。

    不是一次设置和执行5个单独的查询,而是运行 SELECT 一次处理5个结果。

    例如,如果您使用ado.net,请执行以下操作(为简洁起见省略了错误检查;为清晰起见,请执行非循环/非动态检查):

    string sql = "SELECT COUNT(*) FROM Table1; SELECT COUNT(*) FROM Table2;"
    
    SqlCommand cmd = new SqlCommand(sql, connection);
    SqlDataReader dr = cmd.ExecuteReader();
    
    // Defaults to first result set
    dr.Read();
    int table1Count = (int)dr[0];
    
    // Move to second result set
    dr.NextResult();
    dr.Read();
    int table2Count = (int)dr[0];
    

    如果您使用某种orm,比如nhibernate,那么应该有一种方法来启用自动查询批处理。

        5
  •  0
  •   penguat    14 年前

    count(*)可能特别昂贵,因为它可能导致加载(和分页)整个表,而您可能只需要主键上的count(在某些实现中,它是优化的)。

    从它的声音来看,您每次都会导致一个表加载操作,这是很慢的,但除非它运行明显缓慢,或引起某种问题,不要优化:过早和不必要的优化可能会造成很大的麻烦!

    对索引主键的计数要快得多,但由于有索引的代价,这可能没有任何好处。

        6
  •  0
  •   SqlACID    14 年前

    所有的I/O都是昂贵的,如果没有它你可以完成任务,你应该这样做。但如果需要的话,我不会担心的。

    您提到了在视图状态下存储计数,当然这是一个选项,只要该计数错误时代码的行为是可接受的,因为底层记录已丢失或已添加到。

        7
  •  0
  •   DraÅ¡ko    14 年前

    这取决于您如何处理此表中的数据。如果它们经常变化,而您每次都需要它们,那么您可以创建触发器,它将填充另一个仅包含此表计数的表。如果需要单独显示这些数据,可能只需对一个特定表执行“select count(*)…”。我立刻想到了这一点,但我敢肯定还有其他方法可以加快速度。可能是缓存数据?:)