快速计算非常大的表中确切行数的最快方法?


234

我遇到过的文章指出,SELECT COUNT(*) FROM TABLE_NAME当表中有很多行和很多列时,它会变慢。

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

在回答之前,请考虑以下几点:

  • 我正在寻找与数据库供应商无关的解决方案。如果它涵盖MySQLOracleMS SQL Server,则可以。但是,如果确实没有独立于数据库供应商的解决方案,那么我将为不同的数据库供应商寻求不同的解决方案。

  • 我不能使用任何其他外部工具来执行此操作。我主要是在寻找基于SQL的解决方案。

  • 我无法再对数据库设计进行规范化。它已经在3NF中,而且围绕它已经编写了很多代码。


4
只是好奇为什么当您有数十亿行时为什么需要确切的即时行数?
zerkms 2011年

2
我们不是都希望我们的数据库供应商已经优化了此特定结构吗?
KevinDTimm

5
@Swaranga,您能否进一步说明该数据库维护的目的是必须知道表中确切的行数?我无法想象 正如凯文所说,如果有一种比COUNT(*)更快的方法,那么DBMS供应商一定会(应该)重新实现COUNT(*)以使用它……
Tony Andrews

3
当然,如果经常写入表,那么您的确切计数仅在特定时间点才是正确的,并且如果其他进程正在向表写入,甚至可能不准确,除非您在查询上加了表锁。
史蒂夫·福特

2
您可以使用插入和删除触发器来保持滚动计数吗?
狗仔队2013年

Answers:


246

简单答案:

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

笔记:

COUNT(1)= COUNT(*)= COUNT(PrimaryKey)以防万一

编辑:

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

第二个具有较少的行=错误。取决于写入的内容相同或更多(此处删除操作数小时)


9
不,COUNT(*) = COUNT(key)。这是错误的。如果没有NOT NULL约束-那么它们可以不相等(结果以及执行计划中)。
zerkms,2011年

14
@zerkmsby:对于COUNT(key),我的意思是COUNT(primarykey),该值不能为空。我会澄清
-gbn

8
(NOLOCK)不能使它在生产中运行,并且可能导致计数不正确。使用该提示时,请确保它可以防止锁定,但是对生产盒而言,副作用是在某些情况下可以对行进行两次计数,而在其他情况下可以跳过行。NOLOCK最好在未写入的表上使用,因为它允许“脏读”。除非他们完全理解后果,否则不建议人们使用该提示
达沃斯(Davos)2013年

4
@mishrsud唯一准确的查询是SELECT COUNT(*),但是它很慢。您可以选择精确而缓慢,也可以粗略而快速。您的工作将取决于对您而言重要的目的。无论出于何种原因,“无锁”都可能包括或确实排除交易中间或移动页面中的行。
达沃斯

5
@gbn非常好的解决方案,你能告诉我什么用index_id < 2吗?
提交

29

到目前为止,在MySQL上最快的方法是:

SHOW TABLE STATUS;

如果需要的话,您将立即获得所有带有行数(总数)的表以及大量额外信息。


1
聪明的方法..有了这个,你可以在一个查询中获得多个表的行数。
Deval Khandelwal 2014年

您是否在具有约十亿个表(如@gbn)的表的db上运行并注意到了时间?
KNU

数据库中所有表的总行数是哪个值?这些是近似值-如果您想要确切的行计数值怎么办?
Kreeverp'3

2
这根本不起作用,例如,在INNODB上,存储引擎读取了几行并进行推断以猜测行数
Martijn Scheffer

10

我遇到过一些文章,指出当表具有许多行和许多列时,从TABLE_NAME进行SELECT COUNT(*)会很慢。

那取决于数据库。某些方法可以加快计数速度,例如,通过跟踪索引中的行是活的还是死的,从而允许仅扫描索引以提取行数。其他人则不需要,因此需要访问整个表并逐一计数活动行。对于一张巨大的桌子,两者都会变慢。

请注意,通常可以使用查询优化工具,表统计信息等来提取良好的估计。例如,对于PostgreSQL,您可以解析输出explain count(*) from yourtable并获得合理的行数估计。这使我想到了第二个问题。

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

认真吗 :-)您真正的意思是从具有数十亿行的表中获得的确切计数吗?你确定吗 :-)

如果确实这样做,则可以使用触发器来跟踪总数,但是请注意并发和死锁。


是的丹尼斯,需要准确的计数。:(
Swaranga Sarma

5
幸运的是,Google经理比您的老板更合理。。。想象一下,如果为每个查询返回准确数量的搜索结果而不是坚持估计的数量,将会有多慢。
丹尼斯·德伯纳迪

至少你对我很同情。唯一的Oracle解决方案怎么样?那将在一定程度上减少我的问题。目前,客户正在使用Oracle。因此,如果我想出一种仅针对Oracle的解决方法,那么(暂时)可以。:)
Swaranga Sarma

6
“是的,丹尼斯,需要精确的计数。 A并更新表B中的行...?还是比这更疯狂?;-)
Tony Andrews

1
事务2在提交事务1之前无法开始。没有“ counts table”更新,许多更新事务可以并行运行。使用“计数表”,每笔交易都必须“获得票证”以更新其计数。因此,交易开始在售票机上排队(调度程序确定谁将成为下一个锁定计数表的人)。
Erwin Smout,

10

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

只是为了回答您的问题,

如果您需要一种独立于DBMS的方法,最快的方法将始终是:

SELECT COUNT(*) FROM TableName

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

COUNT(*) 无论如何,应该由DBMS(至少是任何值得PROD的DB)进行优化,因此请勿尝试绕过其优化。

附带一提:
由于表格大小,我敢肯定您的许多其他查询也需要很长时间才能完成。任何性能问题都应通过考虑速度来考虑方案设计来解决。我意识到您说过更改不是一种选择,但是事实证明,超过10分钟的查询也不是一种选择。3 NF并不总是最好的方法,当你需要的速度,有时数据可以在多个表中,如果记录不就被划分具有存储在一起。需要考虑的事情...


10

我从另一个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亿条记录,并且上述返回在不到1ms的时间内完成。与此同时,

SELECT COUNT(id) FROM MyTable

需要整整39分钟52秒!

它们产生的行数完全相同(在我的情况下为519326012)。

我不知道情况是否总是如此。


您可以添加一个参数来获取此查询的行数吗?示例:从TABLENAME WHERE ColumnFiled ='1'中选择COUNT(1)与您的查询?
VnDevil

那就是计数-在这种情况下,行(记录)数就是“计数”。“ 5亿条记录”是一个近似数,“ 519326012”是确切的行数或计数。行=记录=计数。
JakeJ

9

您可以尝试使用此sp_spaceused(Transact-SQL)

显示行数,保留的磁盘空间以及当前数据库中的表,索引视图或Service Broker队列使用的磁盘空间,或显示整个数据库保留和使用的磁盘空间。


sp_spaceused不会给我一个近似数吗?
Swaranga Sarma

1
仅供参考:这在内部使用sys.dm_db_partition_stats
gbn

6

如果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的将来版本中使用它,因为它可能会在不久的将来删除。

示例代码取自:如何快速而无痛地获取表行计数


大概是不准确的:请查看我的答案
gbn

您知道不正确的示例吗?AFAIK,它不依赖于更新的统计信息。
Alireza Maddah


5

我没有其他回答者那么专业,但是我在从表中选择随机行的过程遇到了问题(不是太相关),但是我需要知道参考表中的行数计算随机指数。使用传统的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中的查询时间为零。


1
FWIW,您应该提到您正在使用的WHICH数据库供应商;我认为该声明将根据供应商而有所不同。
ToolmakerSteve

5

好吧,晚了5年,不确定是否有帮助:

我试图数数。使用MS SQL Server Management Studio的SQL Server表中的行数,并遇到一些溢出错误,然后使用以下代码:

从[dbname]。[dbo]中选择count_big(1)。[FactSampleValue];

结果 :

24296650578行


5

我发现这篇很好的文章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 

注释:

快速获取行数的方法。取决于统计信息,并且不准确。

使用COUNT_ROWS运行DBCC UPDATEUSAGE(Database),对于大型表可能要花费大量时间。

方法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 Management Studio计数行的方式(查看表属性,存储,行数)。速度非常快,但行数仍然近似。

方法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); 

注释:

快速(尽管不如方法2快)操作,并且同样重要,可靠。


谢谢!确实有用的提示。我没有查看系统表的权限,因此方法4不是我。但是方法3足够好。
尼古拉斯·汉弗莱

3

我不认为有一个通用的始终最快的解决方案:某些RDBMS /版本对SELECT COUNT(*)使用更快的选项进行了特定的优化,而另一些则仅进行表扫描。您需要转到第二套文档/支持站点,这可能需要编写一些更具体的查询,通常是某种以某种方式命中索引的查询。

编辑:

根据您的架构和数据分布,这可能会起作用:是否有一个索引列引用一个递增的值,一个数字递增的ID(例如,甚至是时间戳记或日期)?然后,假设不发生删除,则应该可以将计数存储为某个最近值(昨天的日期,某个最近采样点的最高ID值),然后再添加该计数,这应该可以在索引中快速解决。当然,它非常依赖于值和索引,但几乎适用于任何DBMS的任何版本。


我非常希望任何体面的DBMS都可以使用的索引SELECT COUNT(*)。甚至MySQL显然也做到了...。
sleske,2011年

假设删除不会发生 -严重吗?; p
ToolmakerSteve16年

3

我对这个问题迟到了,但是这里是您可以使用MySQL进行的操作(因为我使用MySQL)。我在这里分享我的意见:

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

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

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

结果
行数:511235
控制台输出:受影响的行:0找到的行:1警告:0 1个查询的持续时间:0.250秒摘要:行数不正确。

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

结果
行数:507806
控制台输出:受影响的行:0找到的行:48警告:0 1个查询的持续时间:1.701秒。
行数不准确。

我不是MySQL或数据库专家,但是我发现对于非常大的表,可以使用选项2或3来“了解”多少行。

我需要获取这些行数以在UI上显示一些统计信息。通过以上查询,我知道总行数超过500,000,因此我想出了显示“超过500,000行”之类的统计信息,但未显示确切的行数。

也许我还没有真正回答OP的问题,但是我正在分享在需要此类统计信息的情况下所做的事情。在我的情况下,显示大约的行是可以接受的,因此以上内容对我来说很有用。


2

并非完全与DBMS无关的解决方案,但至少您的客户端代码看不到区别...

创建仅具有一行和一个整数字段N 1的另一个表T ,并创建仅执行的INSERT TRIGGER:

UPDATE T SET N = N + 1

还创建一个执行以下操作的DELETE TRIGGER:

UPDATE T SET N = N - 1

值得一提的DBMS将保证2以上的操作的原子性,并且N将始终包含准确的行数,因此通过以下操作可以非常快速地获得:

SELECT N FROM T

尽管触发器是特定于DBMS的,但不是从T中进行选择,并且不需要为每个受支持的DBMS更改客户端代码。

但是,如果表是INSERT或DELETE密集型表,则可能会遇到一些可伸缩性问题,尤其是在INSERT / DELETE之后不立即提交时。


1这些名称只是占位符-在生产中使用更有意义的名称。

2即,只要在单个SQL语句中完成读取和写入操作,就无法通过在写入和写入N之间的并发事务来更改N。


2

一个字面上的疯狂答案,但是如果您设置了某种复制系统(对于具有十亿行的系统,希望如此),则可以使用粗略估计器(例如 MAX(pk)),将该值除以从属数您可以并行运行多个查询。

在大多数情况下,您将以最佳方式(或我猜的主键)在从属服务器上对查询进行分区,方式是这样(我们将使用250000000作为行/从属设备):

-- 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)

因此,您不仅需要在自己的奴隶中运行选择,还需要执行插入操作,类似于此:

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

从站向主站上的表写入数据可能会遇到问题。您可能需要变得更加悲伤-我是说,富有创造力:

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

最后,相对于第一个从属服务器,在复制图所遍历的路径中最后应该存在一个从属服务器。该从站现在应该具有所有其他计数器值,并且应该具有自己的值。但是,当您完成操作时,可能已经添加了几行,因此您必须插入另一行,以补偿counter_table中记录的最大pk和当前的最大pk。

到那时,您必须执行一个汇总函数来计算总行数,但这会更容易,因为您最多只能在“拥有并更改的从站数量”行上运行它。

如果您在从属服务器中有单独的表,则可以UNION获取所需的所有行。

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

或者您知道,可以将数据迁移到分布式处理系统上,或者再使用数据仓库解决方案(这也将使您将来处理令人敬畏的数据)更加省力。

请注意,这确实取决于您的复制设置得如何。由于主要瓶颈很可能是持久性存储,因此,如果您的存储空间比较粗糙,或者数据存储的隔离性很差,并且邻居噪音很大,那么这可能会比仅等待单个存储慢一些。SELECT COUNT(*) ...

但是,如果您具有良好的复制能力,那么您的速度提升应该与数量或从属数量直接相关。实际上,如果仅运行计数查询需要10分钟,并且您有8个从属,则您可以将时间减少到不到两分钟。可能需要一个小时来解决此解决方案的细节。

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

实际上,这似乎是不可能的,因为您基本上只能使用仅SQL的解决方案,而且我认为您没有提供一种可在多个从属服务器上立即运行分片和锁定查询的机制。也许,如果您控制了复制日志文件……这意味着您实际上是为此目的而从动纺纱奴隶,这无疑比仅在单台机器上运行计数查询要慢。

因此,我有两个便士。


2

如果插入触发器的使用成本太高,但可以提供删除触发器,并且有一个自动递增id,则在对整个表计数一次之后,将计数记为last-countlast-counted-id

然后每天只需要计算id> last-counted-id,将其添加last-count并存储新的last-counted-id

如果删除的记录的ID <= last-counted-id,则删除触发器将减少last-count。


..很抱歉,没有时间显示将要使用的SQL(我的SQL生锈了)。如果有人想编辑我的答案以添加SQL,那就太好了!
ToolmakerSteve

1

如果您有一个带有自动递增主键列的典型表结构,其中的行从未删除,则以下将是确定记录数的最快方法,并且在大多数符合ANSI的数据库中应以类似的方式工作:

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

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


1
并非完全正确-如果INSERT交易回滚怎么办?该主键值将不存在,因此实际记录数将比最大值少一。
Crispalot爵士,13年

可能是顺序上的差距。通常是回滚的结果。
Osa E 2014年

实际上,count(*)如果数据库供应商没有充分优化,则此答案的修改速度可能会大大快于count(*):每天跟踪最后一个自动索引及其对应的计数,然后索取超过该值的记录数。delete如果在delete上添加触发器以减少前一个总数(如果删除的记录id <=最后一个自动索引),则还可以处理。
ToolmakerSteve

1

对于sql server,请尝试以下操作

SELECT T.name, 
       I.rows AS [ROWCOUNT] 
FROM   sys.tables AS T 
       INNER JOIN sys.sysindexes AS I 
               ON T.object_id = I.id AND I.indid < 2 
WHERE T.name = 'Your_Table_Name'
ORDER  BY I.rows DESC 

0

从sysindexes中选择id = Object_ID('TableName')并且indid <2的行


0

在某个列上放置一个索引。这应该使优化器可以对索引块执行完整扫描,而不是对表进行完整扫描。这将减少您的IO成本。看一下之前和之后的执行计划。然后双向测量挂钟时间。


如果一个表有数十亿行,但任何列上都没有索引,那么将存在广泛的性能问题,远远超出了原始问题中所表达的需求..但是,您提到的好(假设什么都不做!):)
ToolmakerSteve

0

如果您使用的是Oracle,该如何做(假设表统计信息已更新):

select <TABLE_NAME>, num_rows, last_analyzed from user_tables

last_analyzed将显示上一次收集统计信息的时间。


0

使用PostgreSQL:

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

-1

在SQL Server 2016中,我可以仅检查表属性,然后选择``存储''选项卡-这将为我提供行数,表使用的磁盘空间,使用的索引空间等。


他正在寻找一个database vendor independent solution。同样,这需要GUI,并且不能自动化。此外,它是不是更快的COUNT(*)
弗里德

-3

也许有点晚了,但这可能对其他人帮助MSSQL

;与RecordCount AS(SELECT ROW_NUMBER()OVER(ORDER BY COLUMN_NAME)AS AS [RowNumber] FROM TABLE_NAME)SELECT MAX(RowNumber)FROM RecordCount


这比COUNT()明显更令人担忧,除非我们非常幸运,并且优化器设法将其优化为COUNT()-为什么要在随机列上对它进行排序?!
dsz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.