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

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

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

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


当前回答

这是一个疯狂的答案,但是如果你设置了某种复制系统(对于一个有十亿行的系统,我希望你这样做),你可以使用一个粗略的估计器(如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年的两枚硬币。

其他回答

如果SQL Server版本是2005/2008,您可以使用dmv来计算表中的行数:

-- Shows all user tables and row counts for the current database 
-- Remove is_ms_shipped = 0 check to include system objects 
-- i.index_id < 2 indicates clustered index (1) or hash table (0) 
SELECT o.name, 
 ddps.row_count 
FROM sys.indexes AS i 
 INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID 
 INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID 
 AND i.index_id = ddps.index_id 
WHERE i.index_id < 2 
 AND o.is_ms_shipped = 0 
ORDER BY o.NAME 

对于SQL Server 2000数据库引擎,sysindexes可以工作,但强烈建议避免在将来的SQL Server版本中使用它,因为它可能在不久的将来被删除。

示例代码摘自:如何快速轻松地获取表行计数

有没有更好的方法来获得一个表的行数的精确计数?

简单地回答你的问题,没有。

如果你需要一个独立于DBMS的方法来做这件事,最快的方法总是:

SELECT COUNT(*) FROM TableName

一些DBMS供应商可能有更快的方法,只适用于他们的系统。其中一些选项已经在其他答案中发布了。

COUNT(*)应该由DBMS(至少是任何值得PROD的DB)进行优化,所以不要试图绕过它们的优化。

On a side note: I am sure many of your other queries also take a long time to finish because of your table size. Any performance concerns should probably be addressed by thinking about your schema design with speed in mind. I realize you said that it is not an option to change but it might turn out that 10+ minute queries aren't an option either. 3rd NF is not always the best approach when you need speed, and sometimes data can be partitioned in several tables if the records don't have to be stored together. Something to think about...

PostgreSQL:

SELECT reltuples AS approximate_row_count FROM pg_class WHERE relname = 'table_name'

如果使用插入触发器代价太大,但可以使用删除触发器,并且有一个自动递增的id,那么在对整个表进行一次计数后,将计数记为last-count和last- counts -id,

然后每天只需要为id > last- counting -id计数,将其添加到last-count中,并存储新的last- counting -id。

如果被删除记录的id <= last-count -id,删除触发器将递减last-count。

在SQL server 2016中,我可以检查表属性,然后选择“存储”选项卡-这给了我行数,表使用的磁盘空间,使用的索引空间等。