计算一个非常大的表中确切行数的最快方法?
Posted
技术标签:
【中文标题】计算一个非常大的表中确切行数的最快方法?【英文标题】:Fastest way to count exact number of rows in a very large table? 【发布时间】:2011-08-29 11:31:57 【问题描述】:我遇到过一些文章指出,当表格有很多行和很多列时,SELECT COUNT(*) FROM TABLE_NAME
会很慢。
我有一个可能包含数十亿行的表 [它大约有 15 列]。有没有更好的方法来获取表格行数的EXACT计数?
请在回答之前考虑以下几点:
我正在寻找数据库供应商 独立解决方案。如果是就OK 涵盖 MySQL、Oracle、MS SQL Server。 但是如果有真的没有数据库 供应商独立解决方案然后我 将满足于不同的解决方案 针对不同的数据库供应商。
我无法使用任何其他外部工具 去做这个。我主要是找一个 基于 SQL 的解决方案。
我无法规范化我的数据库设计 任何进一步。它已经在 3NF 中,而且是 很多代码已经写好了 在它周围。
【问题讨论】:
难道我们不希望我们的数据库供应商已经优化了这个特殊的结构吗? @Swaranga,您能否详细说明一下这个数据库维护的目的是什么,必须知道表中的确切行数?我无法想象。正如 Kevin 所说,如果有比 COUNT(*) 更快的方法,那么 DBMS 供应商肯定会(应该)重新实现 COUNT(*) 以使用它...... 当然,如果经常写入表,那么您的确切计数只会在特定时间点准确,如果其他进程正在写入表,甚至可能不准确,除非您输入查询上的表锁。 您可以使用插入和删除触发器来保持滚动计数吗? 如果insert trigger
太贵了,但delete trigger
是负担得起的,请参阅我的回答***.com/a/39295280/199364 的方法,对于具有自动增量ID 的表:跟踪计数和每个计数的最后一个ID天,在适当的时候删除触发器递减计数。
【参考方案1】:
使用 SQL Server 2019,您可以使用 APPROX_COUNT_DISTINCT,其中:
返回组中唯一非空值的近似数量
并且来自文档:
APPROX_COUNT_DISTINCT 专为在大数据场景中使用而设计,是 针对以下条件进行了优化:
访问数百万行或更多行的数据集,并且 聚合具有许多不同值的一列或多列
还有函数
实施保证在 97% 的概率内错误率高达 2% 比穷举 COUNT DISTINCT 操作需要更少的内存 与精确的 COUNT DISTINCT 操作相比,较小的内存占用不太可能将内存溢出到磁盘。实现背后的算法是HyperLogLog。
【讨论】:
【参考方案2】:对我来说,在一张非常大的桌子上,
SELECT COUNT(1) FROM TableLarge
需要 37 秒,而
SELECT COUNT_BIG(1) FROM TableLarge
需要 4 秒。
【讨论】:
【参考方案3】:可能有点晚了,但这可能对其他人的 MSSQL 有所帮助
;WITH RecordCount AS ( SELECT ROW_NUMBER() OVER (ORDER BY
COLUMN_NAME) AS [RowNumber] FROM TABLE_NAME ) SELECT
MAX(RowNumber) FROM RecordCount
【讨论】:
这比 COUNT() 差得多,除非我们非常幸运并且优化器设法将其优化为 COUNT() - 为什么要求它对随机列进行排序?!?跨度> 【参考方案4】:select rows from sysindexes
where id = Object_ID('TableName') and indid <2
【讨论】:
【参考方案5】:在 SQL Server 2016 中,我可以检查表属性,然后选择“存储”选项卡 - 这会显示行数、表使用的磁盘空间、使用的索引空间等。
【讨论】:
他正在寻找database vendor independent solution
。这也需要一个 GUI,并且不能自动化。它也不像 COUNT(*) 那样快【参考方案6】:
使用 PostgreSQL:
SELECT reltuples AS approximate_row_count FROM pg_class WHERE relname = 'table_name'
【讨论】:
【参考方案7】:我从另一个 *** 问题/答案中得到了这个脚本:
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)。
我不知道会不会一直这样。
【讨论】:
您可以添加一个参数来获取此查询的行数吗?示例:使用您的查询选择 COUNT(1) FROM TABLENAME WHERE ColumnFiled = '1'? 这是计数 - 在这种情况下,行(记录)的数量是“计数”。 “5 亿条记录”是一个近似数字,“519326012”是确切的行数或计数。行 = 记录 = 计数。 当我对表 (select count(*) from table
) 与此解决方案进行计数时,后者的计数减少了 11。
嗨,有没有办法对两个通过内部连接关联的表做同样的事情?【参考方案8】:
我从martijnh1
中找到了这篇好文章SQL Server–HOW-TO: quickly retrieve accurate row count for table,它对每个场景进行了很好的回顾。
我需要在需要根据特定条件提供计数的地方对此进行扩展,当我计算出这部分时,我会进一步更新这个答案。
同时,以下是文章中的详细信息:
方法一:
查询:
SELECT COUNT(*) FROM Transactions
评论:
执行全表扫描。在大桌子上慢。
方法二:
查询:
SELECT CONVERT(bigint, rows)
FROM sysindexes
WHERE id = OBJECT_ID('Transactions')
AND indid < 2
评论:
检索行数的快速方法。取决于统计数据,不准确。
使用 COUNT_ROWS 运行 DBCC UPDATEUSAGE(Database),这对于大型表可能会花费大量时间。
方法三:
查询:
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 管理工作室计算行数的方式(查看表属性、存储、行数)。非常快,但仍然是近似的行数。
方法四:
查询:
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 的注释。它应该是 SUM(CAST(p.rows AS FLOAT)) 否则在分区表中我们会在输出中得到 n 行。【参考方案9】:简单回答:
数据库供应商独立解决方案 = 使用标准 =COUNT(*)
有近似 SQL Server 解决方案,但不使用 COUNT(*) = out of scope
注意事项:
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
第二个行数少 = 错误。取决于写入是否相同或更多(删除是在几个小时内完成的)
【讨论】:
不,COUNT(*) = COUNT(key)
。这是错误的。如果没有NOT NULL
约束 - 那么它们可能不相等(在结果和执行计划中)。
@zerkmsby:对于 COUNT(key) 我的意思是 COUNT(primarykey) 应该是不可为空的。我会澄清
with (NOLOCK) 不允许它在生产环境中运行,它可能导致计数不准确。当您使用该提示时,请确保它可以防止锁定,但对生产框的副作用是您可以在某些情况下计算行两次或在其他情况下跳过行。 NOLOCK 最好用于未写入的表,因为它允许“脏读”。除非他们完全理解后果,否则不要建议人们使用该提示
@mishrsud 唯一准确的查询是 SELECT COUNT(*),但速度很慢。您可以精确而缓慢,也可以粗糙而快速。您所做的将取决于对于您需要计数的目的而言更重要的是什么。 NO LOCK 可能包括或确实排除处于中间事务或移动页面的行,无论出于何种原因。
@gbn 非常好的解决方案,你能告诉我index_id < 2
的用途吗?【参考方案10】:
对于 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
【讨论】:
【参考方案11】:我迟到了这个问题,但这是你可以用 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 的问题,但我正在分享我在需要此类统计数据的情况下所做的事情。在我的情况下,显示近似行是可以接受的,所以上面的方法对我有用。
【讨论】:
【参考方案12】:如果 insert trigger 使用成本太高,但可以提供 delete trigger,并且有自动增量id
,然后在计算整个表一次后,并记住计数为last-count
和last-counted-id
,
然后每天只需计算id
> last-counted-id
,将其添加到last-count
,并存储新的last-counted-id
。
如果已删除记录的 id
【讨论】:
.. 抱歉没有时间展示将要使用的 SQL(我的 SQL 生锈了)。如果有人想编辑我的答案以添加 SQL,那就太好了!【参考方案13】:好吧,晚了 5 年,不确定是否有帮助:
我试图数数。使用 MS SQL Server Management Studio 的 SQL Server 表中的行数并遇到一些溢出错误,然后我使用了以下内容:
选择 count_big(1) FROM [dbname].[dbo].[FactSampleValue];
结果:
24296650578 行
【讨论】:
【参考方案14】:如果您使用的是 Oracle,这个怎么样(假设表统计信息已更新):
select <TABLE_NAME>, num_rows, last_analyzed from user_tables
last_analyzed 将显示上次收集统计数据的时间。
【讨论】:
【参考方案15】:如果您的典型表结构具有自动递增的主键列,其中的行永远不会被删除,那么以下将是确定记录计数的最快方法,并且在大多数符合 ANSI 的数据库中都应该类似地工作:
SELECT TOP(1) <primarykeyfield> FROM <table> ORDER BY <primarykeyfield> DESC;
我使用包含数十亿行的 MS SQL 表,这些表需要亚秒级的数据响应时间,包括记录计数。相比之下,类似的 SELECT COUNT(*) 需要几分钟来处理。
【讨论】:
不完全正确 - 如果INSERT
事务被回滚怎么办?该主键值将不存在,因此实际记录数将比最大值少一。
可能是序列中的空白。通常是回滚的结果。
实际上,如果数据库供应商没有充分优化 count(*)
,则此答案的修改可能比 count(*)
快得多:每天跟踪最后一次自动索引和其相应的计数,然后询问过去的记录计数。也可以处理delete
s,如果在删除时添加一个触发器以减少 previous 总数,如果删除记录 id
【参考方案16】:
一个真正疯狂的答案,但是如果您设置了某种复制系统(对于具有十亿行的系统,我希望您这样做),您可以使用粗略估计器(如 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 的解决方案中,而且我认为您没有提供一种机制来立即跨多个从属服务器运行分片和锁定查询。也许如果您控制了复制日志文件...这意味着您实际上会为此目的启动从属服务器,这无疑比仅在单台机器上运行计数查询要慢。
这是我 2013 年的两便士。
【讨论】:
【参考方案17】:在某个列上放置一个索引。这应该允许优化器对索引块执行完全扫描,而不是对表进行完全扫描。这将大大降低您的 IO 成本。看前后的执行计划。然后双向测量挂钟时间。
【讨论】:
如果一个表有数十亿行没有任何列上的索引,那么就会出现广泛的性能问题,远远超出原始问题中表达的需要......但你提到这一点很好(假设什么! ) :)【参考方案18】:我远不像其他回答过的人那样专家,但我在使用从表中选择随机行的过程(不是过度相关)时遇到问题,但我需要知道其中的行数我的参考表来计算随机指数。使用传统的 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 中的查询时间为零。
【讨论】:
FWIW,您应该提及您使用的是哪个数据库供应商;我认为声明会因供应商而略有不同。【参考方案19】:不完全是与 DBMS 无关的解决方案,但至少您的客户端代码不会看到差异...
创建另一个只有一行和一个整数字段 N1 的表 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 之后没有立即 COMMIT 时,这可能会产生一些可伸缩性问题。
1 这些名称只是占位符 - 在生产中使用更有意义的名称。
2 即读写 N 之间的并发事务不能更改 N,只要读写都在单个 SQL 语句中完成。
【讨论】:
这可能是一个很棒的技巧。从长远来看,尤其是【参考方案20】:到目前为止,在 MySQL 上最快的方法是:
SHOW TABLE STATUS;
如果需要,您将立即获得所有表格以及行数(即总数)以及大量额外信息。
【讨论】:
聪明的方式..这样你可以在 1 个查询中获得多个表的行数。 您是否在 db 上运行了具有大约十亿个条目(如 @gbn)的表并注意到时间? 数据库中所有表的总行数是哪个值?这些都是近似值 - 如果您想要精确的行计数值怎么办? 这根本不起作用,例如在INNODB上,存储引擎读取几行并推断猜测行数 在没有查询条件的情况下得到一个总数是很好的。但是如果我想要任何查询条件的结果呢?【参考方案21】:我用
select /*+ parallel(a) */ count(1) from table_name a;
【讨论】:
select /*+ parallel(a) */ count(1) from table_name a【参考方案22】:有没有更好的方法来获取表格行数的精确计数?
为了简单地回答您的问题,否。
如果您需要独立于 DBMS 的方式来执行此操作,最快的方式始终是:
SELECT COUNT(*) FROM TableName
一些 DBMS 供应商可能有更快的方法,这些方法只适用于他们的系统。其中一些选项已经发布在其他答案中。
COUNT(*)
无论如何都应该由 DBMS 优化(至少任何值得生产的数据库),所以不要试图绕过他们的优化。
附注: 我敢肯定,由于您的表大小,您的许多其他查询也需要很长时间才能完成。任何性能问题都应该通过考虑速度来考虑您的模式设计来解决。我意识到您说过这不是一种改变的选择,但事实证明 10 分钟以上的查询也不是一种选择。当您需要速度时,第 3 次 NF 并不总是最好的方法,有时如果记录没有必须存储在一起,则可以将数据分区到多个表中。想一想……
【讨论】:
【参考方案23】:我不认为有一个通用的总是最快的解决方案:一些 RDBMS/版本对SELECT COUNT(*)
进行了特定的优化,使用更快的选项,而其他的只是表扫描。您需要访问第二组的文档/支持站点,这可能需要编写一些更具体的查询,通常是以某种方式命中索引的查询。
编辑:
根据您的架构和数据分布,这里有一个可能可行的想法:您是否有一个索引列引用一个递增的值、一个数字递增的 ID,例如,甚至是时间戳或日期?然后,假设没有发生删除,应该可以将计数存储到某个最近的值(昨天的日期,最近某个采样点的最高 ID 值)并添加除此之外的计数,这应该在索引中很快解决.当然,非常依赖于值和索引,但几乎适用于任何 DBMS 的任何版本。
【讨论】:
我非常希望任何体面的 DBMS 都会使用SELECT COUNT(*)
的索引。甚至 MySQL 显然也是这样做的...... .
假设不会发生删除 - 真的吗? ;p【参考方案24】:
如果 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
【讨论】:
这是近似而不是精确:请看我的回答 你知道一个不准确的例子吗? AFAIK,它不依赖于更新的统计数据。【参考方案25】:我遇到过一些文章指出当表有很多行和很多列时,SELECT COUNT(*) FROM TABLE_NAME 会很慢。
这取决于数据库。一些加速计数,例如通过跟踪索引中的行是活的还是死的,允许仅索引扫描以提取行数。其他人则没有,因此需要访问整个表并一一计算活动行数。对于一张巨大的桌子来说,这两种方法都会很慢。
请注意,您通常可以使用查询优化工具、表统计信息等来提取一个好的估计值。例如,在 PostgreSQL 的情况下,您可以解析 explain count(*) from yourtable
的输出并得到一个相当好的估计值的行。这让我想到了你的第二个问题。
我有一个可能包含数十亿行的表 [它大约有 15 列]。有没有更好的方法来获取表格行数的精确计数?
真的吗? :-) 您真的是指具有数十亿行的表中的 exact 计数吗?你真的确定吗? :-)
如果您真的这样做,您可以使用触发器跟踪总数,但如果这样做,请注意并发和死锁。
【讨论】:
幸运的是,谷歌经理比你的老板更通情达理……想象一下,如果它为你的每个查询返回确切的搜索结果数量,而不是坚持估计,那会有多慢号码。 至少你同情我。唯一的 Oracle 解决方案怎么样?这将在一定程度上减少我的问题。目前客户正在使用Oracle;因此,如果我想出一个仅适用于 Oracle 的解决方法,那么 [暂时] 就可以了。 :) 嗯,您可以随时使用触发器更新计数器。不过,请注意并发性。 :-) "是的,丹尼斯,需要准确的计数。:(" - 好吧,我只能推测。数据库维护过程是否发现表 A 中有 42,123,876 行,然后在表 A 中创建 42,123,876 空行表 B,然后循环遍历表 A 并更新表 B 中的行...?还是比这更疯狂?;-) 事务 2 在事务 1 提交之前无法开始。如果没有“计数表”更新,许多更新事务可以并行运行。使用“计数表”,每笔交易都必须“获得一张票”以更新其计数。因此交易开始在售票机上排队(调度程序决定谁将成为下一个获得计数表锁定的人)。【参考方案26】:你可以试试这个sp_spaceused (Transact-SQL)
显示行数,磁盘 保留空间,以及使用的磁盘空间 表、索引视图或服务 当前数据库中的代理队列, 或显示保留的磁盘空间 并被整个数据库使用。
【讨论】:
sp_spaceused 不会给我一个大概的计数吗? 仅供参考:这在内部使用 sys.dm_db_partition_stats以上是关于计算一个非常大的表中确切行数的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章