【问题标题】:SQL Server Count is slowSQL Server 计数很慢
【发布时间】:2012-09-10 20:59:21
【问题描述】:

统计大量数据的表可能会很慢,有时需要几分钟;它还可能在繁忙的服务器上产生死锁。我想显示真实值,NOLOCK 不是一个选项。

我使用的服务器是 SQL Server 2005 或 2008 Standard 或 Enterprise - 如果重要的话。 我可以想象 SQL Server 维护每个表的计数,如果没有 WHERE 子句,我可以很快得到这个数字,对吧?

例如:

SELECT COUNT(*) FROM myTable

应该立即返回正确的值。我是否需要依靠统计数据来更新?

【问题讨论】:

  • 得到一个query execution plan,然后我们可以谈谈...(SELECT COUNT直接查询表而不是使用统计,因为统计可能已经过时了)
  • 愚蠢的问题,但是你有索引吗?
  • @FreshPrinceOfSO 如果您指望Id(bigint,主键,身份规范=true),它仍然很慢。
  • @FreshPrinceOfSO 会有什么不同吗?是的,我实际上有一个聚集索引。
  • 如果经常使用count,那么我们可以将它存储在另一个表中。我也面临同样的问题,我已经使用 IF EXISTS 来解决这个问题。

标签: sql sql-server sql-server-2008 sql-server-2005 count


【解决方案1】:

非常接近的近似值(忽略任何正在进行的交易)将是:

SELECT SUM(p.rows) FROM sys.partitions AS p
  INNER JOIN sys.tables AS t
  ON p.[object_id] = t.[object_id]
  INNER JOIN sys.schemas AS s
  ON s.[schema_id] = t.[schema_id]
  WHERE t.name = N'myTable'
  AND s.name = N'dbo'
  AND p.index_id IN (0,1);

这将比COUNT(*) 更快地返回,并且如果您的表变化得足够快,那么它实际上并没有降低准确度 - 如果您的表在您开始 COUNT(并且已使用锁)和何时发生变化它被返回了(当锁被释放并且所有等待的写事务现在都被允许写入表时),它是否更有价值?我不这么认为。

如果您想要计算表的某个子集(例如,WHERE some_column IS NULL),您可以在该列上创建一个过滤索引,并以一种或另一种方式构造 where 子句,具体取决于它是否是异常或规则(因此在较小的集合上创建过滤索引)。所以这两个索引之一:

CREATE INDEX IAmTheException ON dbo.table(some_column)
  WHERE some_column IS NULL;

CREATE INDEX IAmTheRule ON dbo.table(some_column)
  WHERE some_column IS NOT NULL;

然后您可以使用类似的方式获得计数:

SELECT SUM(p.rows) FROM sys.partitions AS p
  INNER JOIN sys.tables AS t
  ON p.[object_id] = t.[object_id]
  INNER JOIN sys.schemas AS s
  ON s.[schema_id] = t.[schema_id]
  INNER JOIN sys.indexes AS i
  ON p.index_id = i.index_id
  WHERE t.name = N'myTable'
  AND s.name = N'dbo'
  AND i.name = N'IAmTheException' -- or N'IAmTheRule'
  AND p.index_id IN (0,1);

如果你想知道相反的情况,你只需从上面的第一个查询中减去。

【讨论】:

  • 也可以在没有索引时使用。
  • @Adi 对 sys.partitions 的查询需要很长时间?我觉得这很难相信。
  • @Adi 不,这是针对独立的整张桌子的。如果您有一个与 WHERE 子句匹配的过滤索引,则可以专门检查该 index_id。对于 JOIN,我认为绕过计数的唯一方法是 e 拥有索引视图...
  • 此时此答案中的查询似乎确实适用于 Azure Sql。我正在 P2 数据库中测试一个有 200M+ 行并且每分钟批量插入大约 5K 新行的表。此答案中的查询几乎立即返回,天真的 SELECT COUNT(*) 大约需要一分钟(它使用索引扫描)。两者都返回相同的数字。
  • @SarojShrestha 更新了解决该案例的答案。
【解决方案2】:

我已经使用 SSMS 十多年了,直到去年才发现它可以快速轻松地为您提供这些信息,这要感谢this answer

  1. 从数据库树(对象资源管理器)中选择“表”文件夹
  2. 按 F7 或选择查看 > 对象浏览器详情打开对象浏览器详情视图
  3. 在此视图中,您可以右键单击列标题以选择要查看的列,包括已使用的表空间、已使用的索引空间和行数:

请注意,Azure SQL 数据库对此的支持充其量似乎有点参差不齐 - 我的猜测是来自 SSMS 的查询超时,因此每次刷新它只返回少数表,但突出显示的表似乎总是被退回。

【讨论】:

    【解决方案3】:

    如果您只需要粗略计算行数,即。要确保正确加载表或确保数据未被删除,请执行以下操作:

    MySQL> connect information_schema;
    MySQL> select table_name,table_rows from tables;
    

    【讨论】:

    • OP问题与MySQL无关。
    【解决方案4】:

    Count 将执行表扫描或索引扫描。因此,对于大量行,它会很慢。如果你经常做这个操作,最好的办法是把计数记录保存在另一个表中。

    如果您不想这样做,您可以创建一个虚拟索引(您的查询不会使用该索引)并查询它的项目数,例如:

    select 
        row_count
    from sys.dm_db_partition_stats as p
    inner join sys.indexes as i 
      on p.index_id = i.index_id
      and p.object_id = i.object_id
    where   i.name = 'your index'
    

    我建议创建一个新索引,因为这个索引(如果不使用的话)在其他操作期间不会被锁定。

    正如 Aaron Bertrand 所说,维护查询可能比使用现有查询更昂贵。所以选择权在你。

    【讨论】:

    • 但是即使这个索引不用于其他的read操作,对于其他的DML还是要维护的。我认为这个虚拟索引比你想象的要昂贵。
    • 可能如你所说。它必须经过测试。可以在不实际创建新索引的情况下使用 sql,但可以在现有索引上使用。我在过滤索引上使用了类似的东西。我从来不需要真正从头到尾数桌​​子。
    【解决方案5】:

    (“大量数据”有多大? - 应该先评论一下,但也许下面的 exec 已经帮助你了)

    如果我在静态表上运行查询(意味着在很长一段时间内没有其他人对读/写/更新感到烦恼,所以争用不是问题)我的表上有 2 亿行和 COUNT(*) 在 15 秒内开发机器(甲骨文)。 考虑到纯数据量,这仍然相当快(至少对我来说)

    正如你所说的 NOLOCK 不是一个选项,你可以考虑

    exec sp_spaceused 'myTable'
    

    也是。

    但这与 NOLOCK 几乎相同(忽略争用 + 删除/更新 afaik)

    【讨论】:

      猜你喜欢
      • 2012-06-25
      • 2015-11-09
      • 1970-01-01
      • 1970-01-01
      • 2013-09-17
      • 2018-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多