在一个非常大的表中计算准确行数的最快方法?

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

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

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

    我正在寻找一个数据库供应商 独立的解决方案。如果是也可以 涵盖MySQL甲骨文MS SQL Server。 但如果有真的没有数据库 供应商独立的解决方案,然后我 会接受不同的解决方案吗

    .使用实例
  • 我不能使用任何外部工具 这样做。我主要是在找一个

  • 我不能规范化我的数据库设计 任何进一步的。它已经在3NF中,而且 很多代码已经写好了 李。< / p > < / >
565256 次浏览

简单的回答是:

  • 数据库供应商独立的解决方案=使用标准= 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 =错误。相同或更多取决于写入(这里的删除是按小时计算的)

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

编辑:

根据您的模式和数据分布,这里有一个可能可行的想法:您是否有一个索引列引用一个递增的值、一个递增的数字ID,或者甚至是一个时间戳或日期?然后,假设不发生删除,应该可以将计数存储到某个最近的值(昨天的日期,最近某个样本点的最高ID值),并添加超出该值的计数,这应该很快就会在索引中解析。当然,非常依赖于值和索引,但适用于几乎任何版本的DBMS。

你可以试试这个sp_spaceused (Transact-SQL)
< / p >

显示行数,磁盘数 预留空间,磁盘占用空间 表、索引视图或服务 当前数据库中的代理队列, 或显示预留的磁盘空间

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

这取决于数据库。有些方法可以加速计数,例如通过跟踪索引中的行是活的还是死的,从而允许只扫描索引来提取行数。其他的则不是,因此需要访问整个表并逐个计算活动行。对于一张大桌子来说,这两种方式都很慢。

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

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

严重吗?:-)你真的是指一个有数十亿行的表中的确切的计数吗?你真的确定吗?: -)

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

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

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

如果你正在使用Oracle,那么这个怎么样(假设表的统计信息更新了):

select <TABLE_NAME>, num_rows, last_analyzed from user_tables

Last_analyzed将显示上次收集统计数据的时间。

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

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

如果你需要一种独立于DBMS的方式来做这件事,最快的方式将永远是:

SELECT COUNT(*) FROM TableName

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

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

边注:
我相信,由于表的大小,您的许多其他查询也需要很长时间才能完成。任何性能方面的问题都应该在考虑模式设计时考虑到速度。我知道你说过这不是一个可以改变的选项,但结果可能是10分钟以上的查询也不是一个选项。当你需要速度时,第3 NF并不总是最好的方法,有时如果记录不存储在一起,数据可以被划分在几个表中。

.

我使用

select /*+ parallel(a) */  count(1) from table_name a;
select rows from sysindexes
where id = Object_ID('TableName') and indid <2

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

SHOW TABLE STATUS;

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

这并不是一个与dbms无关的解决方案,但至少您的客户端代码看不到区别……

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

UPDATE T SET N = N + 1

还可以创建一个DELETE TRIGGER来执行:

UPDATE T SET N = N - 1

一个称职的DBMS将保证__abc0以上操作的原子性,并且N将始终包含准确的行数,然后超级快速地简单地获得:

SELECT N FROM T

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

但是,如果表是INSERT或DELETE密集型的,这可能会有一些可伸缩性问题,特别是如果在INSERT/DELETE之后没有立即提交。


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

也就是说,N不能通过读和写之间的并发事务来改变,只要读和写都在一条SQL语句中完成。

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

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

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

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

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

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

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

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

嗯,晚了5年,不确定这是否有帮助:

我在试着数不。在SQL Server表中使用MS SQL Server管理工作室的行,并遇到一些溢出错误,然后我使用下面的:

select count_big(1) FROM [dbname].[dbo].[FactSampleValue];

结果:

24296650578行

如果插入触发器太贵而无法使用,但可以提供删除触发器,并且存在自动递增id,然后在计数整个表一次,并记住计数为last-countlast-counted-id

那么每一天只需要为id > last-counted-id计数,将其添加到last-count中,并存储新的last-counted-id

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

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

1) SELECT COUNT(*) AS TOTAL_ROWS FROM <TABLE_NAME>
< p > 结果
行计数:508534
控制台输出:Affected rows: 0 Found rows: 1 Warnings: 0 Duration for 1 query: 0.125 sec.
对于行数较多的表,需要一段时间,但行数非常精确
2) SHOW TABLE STATUS or SHOW TABLE STATUS WHERE NAME="<TABLE_NAME>"
< p > 结果
行数:511235
控制台输出:受影响的行:0发现的行:1警告:0一次查询的持续时间:0.250秒 总结:行数不精确
3) SELECT * FROM information_schema.tables WHERE table_schema = DATABASE();
< p > 结果
行数:507806
控制台输出:Affected rows: 0 Found rows: 48 Warnings: 0 Duration for 1 query: 1.701 sec.

.行数不精确

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

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

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

对于Sql服务器试试这个

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

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

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

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

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

评论:

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

我从另一个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毫秒。 与此同时,< / p >

SELECT COUNT(id) FROM MyTable

整整39分52秒!

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

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

PostgreSQL:

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

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

为我准备了一张很大的桌子,

SELECT COUNT(1) FROM TableLarge

花了37秒

SELECT COUNT_BIG(1) FROM TableLarge

只需要4秒钟。

在SQL Server 2019中,你可以使用APPROX_COUNT_DISTINCT,它:

返回组中唯一非空值的大致数目

医生说:

APPROX_COUNT_DISTINCT是为大数据场景而设计的 优化如下条件:

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

还有,函数

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

实现背后的算法是HyperLogLog

使用COUNT_BIG()来获取一个非常大的文件中的记录计数。

SELECT COUNT_BIG(*) FROM TABLENAME;