SQL Server Count 很慢

51

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

我使用的服务器是SQL Server 2005或2008标准版或企业版-如果有关系的话。 我可以想象SQL Server维护每个表的计数,如果没有WHERE子句,我可以很快地得到那个数字,对吗?

举例来说:

SELECT COUNT(*) FROM myTable

应该立即以正确的值返回。我需要依赖更新的统计数据吗?


2
获取一个查询执行计划,然后我们可以谈论... (SELECT COUNT 直接查询表而不使用统计数据,因为统计数据可能已过期) - Justin
2
愚蠢的问题,但是你有索引吗? - Kermit
@FreshPrinceOfSO 如果你依赖于 Id(bigint,主键,标识规范=true),它仍然很慢。 - ANeves
@FreshPrinceOfSO,这会有什么区别吗?是的,我实际上有一个聚集索引。 - Adi
如果计数经常被使用,那么我们可以将其存储在另一个表中。我也遇到了同样的问题,我使用了IF EXISTS来解决这个问题。 - Banketeshvar Narayan
我有一个拥有10亿条记录的表格。在特定日期范围内,对其中一个列值进行计数需要20分钟才能得到85000条记录的计数结果。考虑到数据量的大小,我认为这是正常的。 - Michael Fever
5个回答

87
非常接近的近似值(忽略任何在飞行中的交易)将是:
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(*) 快得多,如果您的表正在快速更改,则实际上并不会更不准确 - 如果在您开始计数时(并且已获取锁定)和返回计数时(当释放锁定并允许所有等待写入事务写入表时)表已更改,那么它更有价值吗?我认为不是。
如果您想要计算表的某个子集(例如,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);

如果您想知道相反的结果,只需从上述第一个查询中减去即可。

2
@Adi 对 sys.partitions 进行的查询花了很长时间?我觉得很难相信。 - Aaron Bertrand
1
@Adi 不,这是针对独立的、完整的表。如果您有一个与 WHERE 子句匹配的过滤索引,您可以检查该 index_id。对于 JOIN,我认为唯一绕过计数的方法是拥有一个索引视图... - Aaron Bertrand
相关:http://blogs.msdn.com/b/martijnh/archive/2010/07/15/sql-server-how-to-quickly-retrieve-accurate-row-count-for-table.aspx dm_db_partition_stats 适用于 Azure。 - Imran Qadir Baksh - Baloch
4
这个答案中的查询似乎在目前的 Azure Sql 上可以运行。我在一个包含 2 亿多行数据和每分钟批量插入约 5K 行数据的 P2 数据库中进行测试,这个查询几乎立即返回结果,而简单的 SELECT COUNT(*) 查询大约需要一分钟(它使用索引扫描)。两者返回的数字相同。 - quentin-starin
1
@SarojShrestha 更新了答案以解决该情况。 - Aaron Bertrand
显示剩余16条评论

12

“大量数据”指的是多大的数据量?- 应该先评论一下这个问题,但也许下面的代码会帮助你。

如果我在我的开发机器上(使用Oracle),查询一个静态表(意味着相当长一段时间内没有其他人进行读/写/更新,因此争用不是一个问题)的 2 亿行数据并在 15 秒内 COUNT(*),考虑到数据量的纯粹数量,这仍然相当快(至少对我来说是这样)。

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

exec sp_spaceused 'myTable'

但这几乎与NOLOCK相同(忽略争用+删除/更新,据我所知)。


7

我使用SSMS已经十多年了,但是在过去的一年中才发现它可以通过这个答案快速、轻松地提供这些信息。

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

请注意,在Azure SQL数据库中对此的支持似乎最好是稀疏的 - 我猜测是来自SSMS的查询超时,因此每次刷新只返回少量表,但始终返回突出显示的表。


2

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所说,维护查询可能比使用已经存在的索引更加昂贵。所以选择权在你手中。

但是即使这个索引不用于其他读取操作,它仍然必须为其他DML维护。我认为这个虚拟索引比你想象的更昂贵。 - Aaron Bertrand
可能就像你所说的那样,但还需要进行测试。可以在现有索引上使用SQL而无需创建新索引。我曾在过滤索引上使用类似的方法,从未需要从头到尾实际计算表格。 - Dumitrescu Bogdan

-5
如果您只需要大致计算行数,例如确保表格已正确加载或确保数据未被删除,请执行以下操作:
MySQL> connect information_schema;
MySQL> select table_name,table_rows from tables;

这个OP的问题与MySQL无关。 - user6039980

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接