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

时间:2011-05-20 08:18:30

标签: sql database

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

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

请在回答之前考虑以下事项:

  • 我正在寻找数据库供应商 独立解决方案如果它可以 涵盖 MySQL Oracle MS SQL Server 。 但是如果真的没有数据库 供应商独立解决方案然后我 将解决不同的解决方案 适用于不同的数据库供应商。

  • 我不能使用任何其他外部工具 去做这个。我主要是在寻找一个 基于SQL的解决方案。

  • 我无法规范化我的数据库设计 进一步。它已经在3NF,而且还有一个 很多代码已经写好了 围绕着它。

27 个答案:

答案 0 :(得分:224)

简单回答:

  • 数据库供应商独立解决方案=使用标准= 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

第二个行数较少=错误。取决于写入将是相同或更多(删除是在几小时内完成的)

答案 1 :(得分:25)

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

SHOW TABLE STATUS;

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

答案 2 :(得分:10)

  

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

这取决于数据库。一些加速计数,例如通过跟踪索引中的行是活的还是死的,允许仅索引扫描来提取行数。其他人则没有,因此需要访问整个表格并逐个计算实时行。对于一张巨大的桌子,要么会很慢。

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

  

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

真的? :-)你真的是指具有数十亿行的表中的精确计数?你真的相信吗? : - )

如果真的做了,你可以跟踪总的使用触发器,但如果你这样做,请注意并发和死锁。

答案 3 :(得分:9)

你可以尝试这个sp_spaceused (Transact-SQL)

  

显示行数,磁盘   空间保留,以及使用的磁盘空间   表,索引视图或服务   当前数据库中的代理队列,   或显示保留的磁盘空间   并由整个数据库使用。

答案 4 :(得分:9)

  

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

简单地回答您的问题,

如果您需要以DBMS独立的方式执行此操作,最快方式将始终为:

SELECT COUNT(*) FROM TableName

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

无论如何,

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

旁注:
我相信您的许多其他查询也需要很长时间才能完成,因为您的表格大小。任何性能问题都应该通过考虑速度考虑您的架构设计来解决。我意识到你说它不是一个改变的选择,但它可能会发现10分钟以上的查询也不是一个选择。当你需要速度时,第三NF并不总是最好的方法,如果记录没有 存储在一起,有时数据可以分成几个表。需要考虑的事情......

答案 5 :(得分:5)

我用

select /*+ parallel(a) */  count(1) from table_name a;

答案 6 :(得分: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中的查询时间为零。

答案 7 :(得分:5)

那么,迟到5年并且不确定它是否有帮助:

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

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

结果:

24296650578行

答案 8 :(得分:4)

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

示例代码取自:How To Get Table Row Counts Quickly And Painlessly

答案 9 :(得分:3)

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

编辑:

这是一个可能有效的想法,具体取决于您的架构和数据分布:您是否有一个索引列引用增加的值,数字增加ID,比如说,甚至是时间戳或日期?然后,假设没有发生删除,应该可以将计数存储到某个最近的值(昨天的日期,最近某个采样点的最高ID值)并添加超出该值的计数,这应该在索引中很快解决。当然,它非常依赖于值和索引,但几乎适用于任何DBMS版本。

答案 10 :(得分: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,所以我想出了像#34;超过500,000行&#34;没有显示确切的行数。

也许我没有真正回答OP的问题,但我正在分享我在需要此类统计数据的情况下所做的事情。在我的情况下,显示大概的行是可以接受的,所以上面对我有用。

答案 11 :(得分:3)

我从另一个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)。

我不知道是否总会如此。

答案 12 :(得分:2)

我从martijnh1找到了这篇好文章SQL Server–HOW-TO: quickly retrieve accurate row count for table,它为每个场景提供了一个很好的回顾。

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

与此同时,以下是文章的详细信息:

方法1:

查询:

SELECT COUNT(*) FROM Transactions 

评论:

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

方法2:

查询:

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

评论:

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

使用COUNT_ROWS运行DBCC UPDATEUSAGE(数据库),这可能会占用大量表的大量时间。

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

评论:

快速(虽然不如方法2快)操作同样重要,可靠。

答案 13 :(得分:2)

如果插入触发器使用起来太昂贵,但可以提供删除触发器,并且有一个自动增量id < / strong>,然后计算整个表一次,并记住计数为last-countlast-counted-id

然后每天只需要计算id&gt; last-counted-id,将其添加到last-count,然后存储新的last-counted-id

删除触发器会减少last-count,如果已删除记录的id&lt; = last-counting-id。

答案 14 :(得分:2)

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

在大多数情况下,你会根据最好的密钥(或者我猜的主键)在奴隶之间划分查询,这样(我们将使用250000000作为我们的Rows / Slaves):< / p>

-- 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的解决方案,而且我认为你没有提供一种机制来立即在多个从属服务器上运行分片和锁定查询。也许你可以控制复制日志文件...这意味着你真的会为了这个目的而旋转奴隶,这无疑比在一台机器上运行计数查询要慢。

所以我有两个2013年的便士。

答案 15 :(得分:2)

不完全是与DBMS无关的解决方案,但至少您的客户端代码不会看到差异......

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

UPDATE T SET N = N + 1

还创建一个执行的DELETE TRIGGER:

UPDATE T SET N = N - 1

一个值得它的盐的DBMS将保证 2 之上的操作的原子性,并且N将始终包含准确的行数,然后通过简单的超快速获得: / p>

SELECT N FROM T

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

但是,如果表是INSERT或DELETE密集型,这可能会产生一些可伸缩性问题,尤其是在INSERT / DELETE之后不立即执行COMMIT时。


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

2 I.e。只要读取和写入都在单个SQL语句中完成,就不能通过读取和写入N之间的并发事务来更改N.

答案 16 :(得分:1)

如果您的典型表结构具有自动递增的主键列,其中行永远不会被删除,以下将是确定记录计数的最快方法,并且应该在大多数符合ANSI的数据库中工作方式相同:

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

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

答案 17 :(得分: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 

答案 18 :(得分:0)

使用PostgreSQL:

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

答案 19 :(得分:0)

在我的一张大桌子上,

SELECT COUNT(1) FROM TableLarge 

需要37秒,而

SELECT COUNT_BIG(1) FROM TableLarge

需要4秒钟。

答案 20 :(得分:0)

在SQL Server 2019中,您可以使用APPROX_COUNT_DISTINCT

返回一组中唯一非空值的大约数量

并从文档中获取:

APPROX_COUNT_DISTINCT设计用于大数据场景,并且 针对以下条件进行了优化:

  • 访问数百万行或更高的数据集,并且
  • 具有许多不同值的一个或多个列的聚合

还有功能

  • 实施保证在97%的概率内高达2%的错误率
  • 比详尽的COUNT DISTINCT操作所需的内存更少
  • 与精确的COUNT DISTINCT操作相比,较小的内存占用量不太可能将内存溢出到磁盘上。

该实现背后的算法HyperLogLog

答案 21 :(得分:0)

在某些列上添加索引。这应该允许优化器执行索引块的完整扫描,而不是对表的完整扫描。这将降低您的IO成本。查看前后的执行计划。然后双向测量挂钟时间。

答案 22 :(得分:0)

从sysindexes中选择行 其中id = Object_ID('TableName')和indid&lt; 2

答案 23 :(得分:0)

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

select <TABLE_NAME>, num_rows, last_analyzed from user_tables

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

答案 24 :(得分:-1)

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

答案 25 :(得分:-2)

如果您的表上某处有一个主键(唯一值),则可以使用{% block content %} <div class="pagination"> <span class="step-links"> ... all standard, works fine </span> </div> {% for record in records %} {% if records.has_next %} <!-- <form id="form_related" method="POST" action="" > --> <form id="form_related" method="POST" action="?page={{ records.next_page_number }}" > {% else %} <form id="form_related" method="POST" action="" > {% endif %} {% csrf_token %} {{ formset.management_form }} <input type="hidden" name="placeid" value="{{ record.placeid }}" /> {% for form in formset %} <div class="row"> <div class="col-sm-4 id="place-record"> <!-- Place attributes --> </div> <div class="col-sm-8" id="hit-forms"> <div id="review"> <span>{{ form.match }} flag: {{ form.flag_geom }}</span> </div> <div id="hit"> <!-- Hit attributes --> </div> </div> </div> {% endfor %} {% endfor %} {% endblock%} 来给您总行数。以下是示例代码段:

MAX(yourId)

答案 26 :(得分:-3)

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

  

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