我曾经读过一些文章,说当表有很多行和很多列时,SELECT COUNT(*) FROM TABLE_NAME将会很慢。

我有一个可能包含数十亿行的表(它大约有15列)。有没有更好的方法来获得一个表的行数的精确计数?

在回答之前请考虑以下问题:

我正在寻找一个数据库供应商 独立的解决方案。如果是也可以 涵盖MySQL, Oracle, MS SQL Server。 但如果真的没有数据库 供应商独立的解决方案,然后我 会接受不同的解决方案吗 针对不同的数据库供应商。 我不能使用任何外部工具 这样做。我主要是在找一个 基于SQL的解决方案。 我不能规范化我的数据库设计 任何进一步的。它已经在3NF中,而且 很多代码已经写好了 围绕它。


当前回答

在某列上放一个索引。这应该允许优化器执行索引块的完整扫描,而不是对表的完整扫描。这将大大降低你的IO成本。看看前后的执行计划。然后用两种方法测量挂钟的时间。

其他回答

我远不及其他回答问题的专家,但我在从表中选择随机行(不太相关)的过程中遇到了问题,但我需要知道我的参考表中的行数来计算随机索引。使用传统的Count(*)或Count(1)可以工作,但我偶尔需要2秒才能运行查询。所以相反(对于我的表命名为'tbl_HighOrder')我使用:

Declare @max int

Select @max = Row_Count
From sys.dm_db_partition_stats
Where Object_Name(Object_Id) = 'tbl_HighOrder'

它工作得很好,在Management Studio中的查询时间为零。

我找到了一篇很好的文章:SQL Server-HOW-TO:快速从martijnh1检索表的准确行数,它很好地概述了每个场景。

我需要在需要根据特定条件提供计数的地方进行扩展,当我计算出这一部分时,我会进一步更新这个答案。

与此同时,以下是文章中的细节:

方法1:

查询:

SELECT COUNT(*) FROM Transactions 

评论:

执行全表扫描。在大桌子上慢点。

方法2:

查询:

SELECT CONVERT(bigint, rows) 
FROM sysindexes 
WHERE id = OBJECT_ID('Transactions') 
AND indid < 2 

评论:

快速检索行数的方法。取决于统计数据,不准确。

运行DBCC UPDATEUSAGE(数据库)WITH COUNT_ROWS,这对于大型表可能会花费大量时间。

方法3:

查询:

SELECT CAST(p.rows AS float) 
FROM sys.tables AS tbl 
INNER JOIN sys.indexes AS idx ON idx.object_id = tbl.object_id and
idx.index_id < 2 
INNER JOIN sys.partitions AS p ON p.object_id=CAST(tbl.object_id AS int) 
AND p.index_id=idx.index_id 
WHERE ((tbl.name=N'Transactions' 
AND SCHEMA_NAME(tbl.schema_id)='dbo')) 

评论:

SQL管理工作室计算行数的方法(查看表属性、存储、行数)。非常快,但仍然是大概的行数。

方法4:

查询:

SELECT SUM (row_count) 
FROM sys.dm_db_partition_stats 
WHERE object_id=OBJECT_ID('Transactions')    
AND (index_id=0 or index_id=1); 

评论:

操作快(虽然不如方法二快),同样重要的是,可靠。

我说这个问题有点晚了,但下面是你可以用MySQL做什么(就像我使用MySQL一样)。我在这里分享我的观察:

1) SELECT COUNT(*) AS TOTAL_ROWS FROM <TABLE_NAME>

结果 行数:508534 控制台输出:受影响的行:0发现的行:1警告:0一次查询的持续时间:0.125秒。 对于有大量行的表需要一些时间,但是行数非常精确。

2) SHOW TABLE STATUS or SHOW TABLE STATUS WHERE NAME="<TABLE_NAME>"

结果 行数:511235 控制台输出:受影响的行:0发现的行:1警告:0一次查询的持续时间:0.250秒 总结:行数不准确。

3) SELECT * FROM information_schema.tables WHERE table_schema = DATABASE();

结果 行数:507806 控制台输出:受影响的行:0发现的行:48警告:0查询一次的持续时间:1.701秒。 行数不准确。

我不是MySQL或数据库专家,但我发现对于非常大的表,你可以使用选项2或3,并得到一个“公平的想法”有多少行。

我需要获得这些行数,以便在UI上显示一些统计信息。通过上面的查询,我知道总行数超过了50万,所以我提出了显示“超过50万行”这样的统计信息,但没有显示确切的行数。

也许我没有真正回答OP的问题,但我在分享我在需要这样的统计数据的情况下所做的事情。在我的情况下,显示大致的行是可以接受的,所以上面的工作对我来说。

这是一个疯狂的答案,但是如果你设置了某种复制系统(对于一个有十亿行的系统,我希望你这样做),你可以使用一个粗略的估计器(如MAX(pk)),用这个值除以你拥有的slave数量,并行运行几个查询。

在大多数情况下,你会根据最佳键(或者我猜是主键)在slave之间划分查询,以这样的方式(我们将使用250000000作为我们的Rows / slaves):

-- First slave
SELECT COUNT(pk) FROM t WHERE pk < 250000000
-- Ith slave where 2 <= I <= N - 1
SELECT COUNT(pk) FROM t WHERE pk >= I*250000000 and pk < (I+1)*250000000
-- Last slave
SELECT COUNT(pk) FROM t WHERE pk > (N-1)*250000000

但是你只需要SQL。真是一团糟。好吧,假设你是个施虐狂。 在主服务器(或最接近的从服务器)上,你很可能需要为此创建一个表:

CREATE TABLE counter_table (minpk integer, maxpk integer, cnt integer, slaveid integer)

因此,除了让select在你的slave中运行之外,你还必须做一个插入,类似于这样:

INSERT INTO counter_table VALUES (I*25000000, (I+1)*250000000, (SELECT COUNT(pk) FROM ... ), @@SLAVE_ID)

当从服务器向主服务器上的表写入数据时,可能会遇到问题。你可能需要更多的sadis——我的意思是,有创意的:

-- A table per slave!
INSERT INTO counter_table_slave_I VALUES (...)

您最终应该拥有一个相对于第一个从机,存在于复制图所遍历路径的最后的从机。该slave现在应该拥有所有其他计数器值,并且应该拥有自己的值。但是当您完成时,可能已经添加了行,所以您必须插入另一行来补偿counter_table中记录的max pk和当前的max pk。

在这一点上,您必须执行一个聚合函数来计算总的行数,但这更容易,因为您将在最多“您拥有和更改的slave数量”的行上运行它。

如果在slave中有单独的表,则可以使用UNION来获得所需的所有行。

SELECT SUM(cnt) FROM (
    SELECT * FROM counter_table_slave_1
      UNION
    SELECT * FROM counter_table_slave_2
      UNION
    ...
  )

或者,不要那么疯狂,将数据迁移到分布式处理系统,或者使用数据仓库解决方案(这也将在未来为您提供出色的数据处理)。

请注意,这取决于复制设置的好坏。由于主要的瓶颈很可能是持久存储,如果您有糟糕的存储或隔离不良的数据存储,并且有严重的邻居噪声,那么这可能会比仅等待一个SELECT COUNT(*)…

但如果你有良好的复制,那么你的速度增益应该直接与数量或奴隶相关。事实上,如果仅运行计数查询就需要10分钟,并且您有8个slave,那么您的时间将缩短到不到几分钟。也许需要一个小时来敲定解决方案的细节。

当然,您永远不会真正得到一个惊人的准确答案,因为这种分布式解决方案引入了一些时间,可以删除和插入行,但您可以尝试在同一实例中获得一个分布式的行锁,并获得特定时刻表中行的精确计数。

实际上,这似乎是不可能的,因为您基本上只能使用sql解决方案,而且我认为您没有提供一种机制来跨多个slave立即运行一个分片和锁定的查询。如果你能控制复制日志文件…这意味着您将为此目的旋转slave,这无疑比仅在一台机器上运行count查询要慢。

这是2013年的两枚硬币。

在某列上放一个索引。这应该允许优化器执行索引块的完整扫描,而不是对表的完整扫描。这将大大降低你的IO成本。看看前后的执行计划。然后用两种方法测量挂钟的时间。