ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

我正在寻找独立于数据库供应商的解决方案。如果它涵盖 MySQL、Oracle、MS SQL Server 就可以了。但如果真的没有独立于数据库供应商的解决方案,那么我将为不同的数据库供应商提供不同的解决方案。

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

我无法进一步规范我的数据库设计。它已经在 3NF 中,而且已经围绕它编写了很多代码。

难道我们不都希望我们的数据库供应商已经优化了这个特定的结构吗?
@Swaranga,您能否详细说明这个数据库维护的目的是什么,必须知道表中的确切行数?我无法想象。正如凯文所说,如果有比 COUNT(*) 更快的方法,那么 DBMS 供应商肯定会(应该)重新实现 COUNT(*) 来使用它......
当然,如果经常写入表,那么您的确切计数只会在特定时间点准确,如果其他进程正在写入表,甚至可能不准确,除非您在查询上放置表锁。
您可以使用插入和删除触发器来保持滚动计数吗?
如果 insert trigger 太贵,但 delete trigger 负担得起,请参阅我的答案 stackoverflow.com/a/39295280/199364 以获取一种方法,对于具有自动递增 id 的表:跟踪每天计数的计数和最后一个 id,当删除触发器递减计数时合适的。

F
Felipe Augusto

简单的回答:

数据库供应商独立解决方案 = 使用标准 = 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 的用途吗?
s
salbahra

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

SHOW TABLE STATUS;

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


聪明的方式..有了这个,你可以在 1 个查询中获得多个表的行数。
您是否在 db 上运行了具有像 @gbn 这样的约十亿个条目的表并注意到时间?
哪个值是数据库中所有表的总行数?这些是近似值 - 如果您想要精确的行计数值怎么办?
这根本不起作用,例如在 INNODB 上,存储引擎读取几行并推断来猜测行数
最好在没有查询条件的情况下获得总计数。但是如果我想要任何查询条件的结果怎么办?
J
JakeJ

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

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


您可以添加一个参数来获取此查询的行数吗?示例: Select COUNT(1) FROM TABLENAME WHERE ColumnFiled = '1' 使用您的查询?
那就是计数 - 在这种情况下,行数(记录)是“计数”。 “5 亿条记录”是一个近似数字,“519326012”是确切的行数或计数。行 = 记录 = 计数。
当我对表 (select count(*) from table) 与此解决方案进行计数时,后者的计数减少了 11。
嗨,有什么方法可以对两个由内部连接相关的表做同样的事情吗?
j
jams

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

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


sp_spaceused 不会给我一个近似的计数吗?
仅供参考:这在内部使用 sys.dm_db_partition_stats
D
Denis de Bernardy

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

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

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

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

严重地? :-) 您真的是指具有数十亿行的表的确切计数吗?你真的确定吗? :-)

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


幸运的是,Google 经理比您的老板更通情达理……想象一下,如果它为您的每个查询返回确切数量的搜索结果而不是坚持估计数字,那将会有多慢。
至少你对我有同感。唯一的 Oracle 解决方案怎么样?这将在一定程度上减少我的问题。目前客户正在使用Oracle;因此,如果我想出一个仅适用于 Oracle 的解决方法,那么 [暂时] 就可以了。 :)
好吧,您总是可以使用触发器来更新计数器。不过,请注意并发性。 :-)
“是的,丹尼斯,需要确切的计数。:(” - 好吧我只能推测。数据库维护过程是否发现表A中有42,123,876行,然后在表B中创建42,123,876个空行,然后循环遍历表A 并更新表 B 中的行...?还是比这更疯狂?;-)
事务 2 不能在事务 1 提交之前开始。如果没有“计数表”更新,许多更新事务可以并行运行。使用“计数表”,每笔交易都必须“获得一张票”以更新其计数。因此交易开始在售票机上排队(调度程序决定谁将成为下一个获得计数表锁定的人)。
J
Jesse Webb

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

简单回答你的问题,不。

如果您需要一种独立于 DBMS 的方式来执行此操作,那么最快的方式始终是:

SELECT COUNT(*) FROM TableName

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

COUNT(*) 无论如何都应该由 DBMS(至少任何值得 PROD 的 DB)进行优化,所以不要试图绕过它们的优化。

附带说明:由于您的表大小,我相信您的许多其他查询也需要很长时间才能完成。任何性能问题都应该通过考虑速度来考虑您的模式设计来解决。我意识到您说过这不是一种改变的选择,但事实证明 10 分钟以上的查询也不是一种选择。当您需要速度时,第 3 种 NF 并不总是最好的方法,如果记录不必存储在一起,有时可以将数据分区到多个表中。有什么要考虑的...


T
Thierry

我从 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 行。
A
Alireza Maddah

如果 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,它不依赖于更新的统计数据。
M
Mainsh S

我用

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

select /*+ parallel(a) */ count(1) from table_name a
j
john rains

我远不像其他回答过的人那样专家,但我在使用从表中选择随机行的过程(不是过度相关)时遇到问题,但我需要知道参考表中的行数计算随机指数。使用传统的 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,您应该提及您使用的是哪个数据库供应商;我认为该声明会因供应商而略有不同。
K
Kaliyug Antagonist

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

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

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

结果 :

24296650578 行


B
Branko Dimitrijevic

不完全是与 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 之后不立即提交时。

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

2 即 N 不能被读写 N 之间的并发事务改变,只要读写都在一个 SQL 语句中完成。


这可能是一个很棒的技巧。从长远来看,尤其是
M
Mike Woodhouse

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

编辑:

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


我非常希望任何体面的 DBMS 都会为 SELECT COUNT(*) 使用索引。甚至 MySQL 显然也是这样做的......
假设删除不会发生 - 严重吗? ;p
s
sunitkatkar

我迟到了这个问题,但这是你可以用 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 的问题,但我正在分享我在需要此类统计数据的情况下所做的事情。就我而言,显示近似行是可以接受的,因此上述内容对我有用。


Y
Yangmun Choi

一个真正疯狂的答案,但是如果您设置了某种复制系统(对于具有十亿行的系统,我希望您这样做),您可以使用粗略估计器(如 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 年的两便士。


T
ToolmakerSteve

如果 insert trigger 使用起来过于昂贵,但可以提供 delete trigger,并且存在自动增量 id,那么在对整个表格进行一次计数并将计数记住为 last-countlast-counted-id 之后,

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

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


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

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

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

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


不完全正确 - 如果 INSERT 事务被回滚怎么办?该主键值将不存在,因此实际记录数将比最大值少一。
可能是顺序的间隙。通常是回滚的结果。
实际上,如果数据库供应商没有充分优化 count(*),则此答案的修改可能比 count(*) 快得多:每天跟踪最后一个自动索引及其相应的计数,然后要求过去的记录计数。还可以处理 delete,如果在删除时添加一个触发器以减少 previous 总数,如果删除记录 id <= 最后一个自动索引。
A
Abhishek B Patel

对于 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 

L
Liam
select rows from sysindexes
where id = Object_ID('TableName') and indid <2

R
Ravi Kiran

在对我来说非常大的桌子上,

SELECT COUNT(1) FROM TableLarge 

需要 37 秒,而

SELECT COUNT_BIG(1) FROM TableLarge

需要 4 秒。


E
EvilTeach

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


如果一个表有数十亿行没有任何列上的索引,那么将存在广泛的性能问题,远远超出原始问题中表达的需求..但是你提到这一点很好(假设什么!):)
D
Diogo Ferreira

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

select <TABLE_NAME>, num_rows, last_analyzed from user_tables

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


D
Dorian

使用 PostgreSQL:

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

g
gotqn

对于 SQL Server 2019,您可以使用 APPROX_COUNT_DISTINCT,它:

返回组中唯一非空值的近似数量

并从文档中:

APPROX_COUNT_DISTINCT 专为在大数据场景中使用而设计,并针对以下情况进行了优化: 访问数百万行或更多行的数据集以及 聚合具有许多不同值的一列或多列

还有,函数

实施保证在 97% 的概率内高达 2% 的错误率

比详尽的 COUNT DISTINCT 操作需要更少的内存

与精确的 COUNT DISTINCT 操作相比,较小的内存占用不太可能将内存溢出到磁盘。

实现其 HyperLogLog 背后的算法。


i
iminiki

使用 COUNT_BIG() 获取超大文件中的记录数。

SELECT COUNT_BIG(*) FROM TABLENAME;

S
SenSei

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


他正在寻找一个database vendor independent solution。这也需要一个 GUI,不能自动化。它也不像 COUNT(*) 那样快
L
Liam

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

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

这比 COUNT() 差得多,除非我们非常幸运并且优化器设法将其优化为 COUNT() - 为什么要求它对随机列进行排序?!?

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅