我曾经读过一些文章,说当表有很多行和很多列时,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年的两枚硬币。

其他回答

简单的回答是:

数据库供应商独立的解决方案=使用标准= COUNT(*) 有近似的SQL Server解决方案,但不要使用COUNT(*) =超出范围

注:

COUNT(1) = COUNT(*) = COUNT(主键)以防万一

编辑:

SQL Server示例(14亿行,12列)

SELECT COUNT(*) FROM MyBigtable WITH (NOLOCK)
-- NOLOCK here is for me only to let me test for this answer: no more, no less

1运行,5分46分钟,计数= 1,401,659,700

--Note, sp_spaceused uses this DMV
SELECT
   Total_Rows= SUM(st.row_count)
FROM
   sys.dm_db_partition_stats st
WHERE
    object_name(object_id) = 'MyBigtable' AND (index_id < 2)

2次,都在1秒内,计数= 1,401,659,670

第二个有较少的rows =错误。相同或更多取决于写入(这里的删除是按小时计算的)

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

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

如果你需要一个独立于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...

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

我从另一个StackOverflow问题/答案得到这个脚本:

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'YourTableNameHere'
  AND s.name = N'dbo'
  AND p.index_id IN (0,1);

我的表有5亿条记录,上面的返回时间不到1毫秒。 与此同时,

SELECT COUNT(id) FROM MyTable

整整39分52秒!

它们产生的行数完全相同(在我的例子中,正好是519326012)。

我不知道情况是否会一直如此。

如果你有一个典型的表结构,其中有一个自动递增的主键列,其中的行永远不会被删除,下面的方法将是确定记录计数的最快方法,并且应该在大多数ANSI兼容的数据库中类似地工作:

SELECT TOP(1) <primarykeyfield> FROM <table> ORDER BY <primarykeyfield> DESC;

我使用的MS SQL表包含数十亿行,需要亚秒级的数据响应时间,包括记录计数。通过比较,类似的SELECT COUNT(*)将花费数分钟来处理。