我遇到过一些文章,其中指出当表有很多行和很多列时,SELECT COUNT(*) FROM TABLE_NAME
会很慢。
我有一个可能包含数十亿行的表[它大约有 15 列]。有没有更好的方法来获得表行数的精确计数?
请在回答之前考虑以下事项:
我正在寻找独立于数据库供应商的解决方案。如果它涵盖 MySQL、Oracle、MS SQL Server 就可以了。但如果真的没有独立于数据库供应商的解决方案,那么我将为不同的数据库供应商提供不同的解决方案。
我不能使用任何其他外部工具来执行此操作。我主要是在寻找基于 SQL 的解决方案。
我无法进一步规范我的数据库设计。它已经在 3NF 中,而且已经围绕它编写了很多代码。
insert trigger
太贵,但 delete trigger
负担得起,请参阅我的答案 stackoverflow.com/a/39295280/199364 以获取一种方法,对于具有自动递增 id 的表:跟踪每天计数的计数和最后一个 id,当删除触发器递减计数时合适的。
简单的回答:
数据库供应商独立解决方案 = 使用标准 = 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
第二个行数较少=错误。取决于写入是否相同或更多(删除是在几个小时内完成的)
到目前为止,MySQL 上最快的方法是:
SHOW TABLE STATUS;
如果需要,您将立即获得所有表的行数(即总数)以及大量额外信息。
我从另一个 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(*) from table
) 与此解决方案进行计数时,后者的计数减少了 11。
你可以试试这个sp_spaceused (Transact-SQL)
显示当前数据库中表、索引视图或Service Broker队列的行数、保留的磁盘空间和使用的磁盘空间,或显示整个数据库保留和使用的磁盘空间。
我遇到过一些文章,指出当表有很多行和很多列时,SELECT COUNT(*) FROM TABLE_NAME 会很慢。
这取决于数据库。一些加速计数,例如通过跟踪索引中的行是活的还是死的,允许仅索引扫描以提取行数。其他人则没有,因此需要访问整个表并一一计算活动行数。对于一张巨大的桌子来说,要么很慢。
请注意,您通常可以使用查询优化工具、表统计信息等来提取一个好的估计值。例如,在 PostgreSQL 的情况下,您可以解析 explain count(*) from yourtable
的输出并获得相当好的行数估计值。这让我想到了你的第二个问题。
我有一个可能包含数十亿行的表[它大约有 15 列]。有没有更好的方法来获得表行数的精确计数?
严重地? :-) 您真的是指具有数十亿行的表的确切计数吗?你真的确定吗? :-)
如果你真的这样做,你可以跟踪使用触发器的总数,但如果你这样做,请注意并发和死锁。
有没有更好的方法来获得表行数的精确计数?
简单回答你的问题,不。
如果您需要一种独立于 DBMS 的方式来执行此操作,那么最快的方式始终是:
SELECT COUNT(*) FROM TableName
一些 DBMS 供应商可能有更快的方法,这些方法只适用于他们的系统。其中一些选项已经发布在其他答案中。
COUNT(*)
无论如何都应该由 DBMS(至少任何值得 PROD 的 DB)进行优化,所以不要试图绕过它们的优化。
附带说明:由于您的表大小,我相信您的许多其他查询也需要很长时间才能完成。任何性能问题都应该通过考虑速度来考虑您的模式设计来解决。我意识到您说过这不是一种改变的选择,但事实证明 10 分钟以上的查询也不是一种选择。当您需要速度时,第 3 种 NF 并不总是最好的方法,如果记录不必存储在一起,有时可以将数据分区到多个表中。有什么要考虑的...
我从 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 快)操作并且同样重要、可靠。
如果 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
我用
select /*+ parallel(a) */ count(1) from table_name a;
我远不像其他回答过的人那样专家,但我在使用从表中选择随机行的过程(不是过度相关)时遇到问题,但我需要知道参考表中的行数计算随机指数。使用传统的 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 中的查询时间为零。
好吧,晚了 5 年,不确定是否有帮助:
我试图数数。使用 MS SQL Server Management Studio 的 SQL Server 表中的行并遇到一些溢出错误,然后我使用了以下内容:
从 [dbname].[dbo].[FactSampleValue] 中选择 count_big(1);
结果 :
24296650578 行
不完全是与 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 语句中完成。
我不认为有一个通用的总是最快的解决方案:一些 RDBMS/版本对 SELECT COUNT(*)
进行了特定优化,使用更快的选项,而其他只是表扫描。您需要访问第二组的文档/支持站点,这可能需要编写一些更具体的查询,通常是以某种方式命中索引的查询。
编辑:
根据您的架构和数据分布,这里有一个可能可行的想法:您是否有一个索引列引用一个递增的值、一个数字递增的 ID,例如,甚至是时间戳或日期?然后,假设没有发生删除,应该可以将计数存储到某个最近的值(昨天的日期,最近某个采样点的最高 ID 值)并添加除此之外的计数,这应该在索引中很快解决.当然,非常依赖于值和索引,但几乎适用于任何 DBMS 的任何版本。
SELECT COUNT(*)
使用索引。甚至 MySQL 显然也是这样做的......
我迟到了这个问题,但这是你可以用 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 的问题,但我正在分享我在需要此类统计数据的情况下所做的事情。就我而言,显示近似行是可以接受的,因此上述内容对我有用。
一个真正疯狂的答案,但是如果您设置了某种复制系统(对于具有十亿行的系统,我希望您这样做),您可以使用粗略估计器(如 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 年的两便士。
如果 insert trigger 使用起来过于昂贵,但可以提供 delete trigger,并且存在自动增量 id
,那么在对整个表格进行一次计数并将计数记住为 last-count
和 last-counted-id
之后,
那么每一天只需要算上id
> last-counted-id
,将其添加到 last-count
,并存储新的 last-counted-id
。
如果已删除记录的 id <= last-counted-id,则删除触发器将减少 last-count。
如果您有一个典型的表结构,其中的行永远不会被删除,并且具有自动递增的主键列,那么以下将是确定记录数的最快方法,并且应该在大多数符合 ANSI 的数据库中类似地工作:
SELECT TOP(1) <primarykeyfield> FROM <table> ORDER BY <primarykeyfield> DESC;
我使用包含数十亿行的 MS SQL 表,这些行需要亚秒级的数据响应时间,包括记录计数。相比之下,类似的 SELECT COUNT(*) 需要几分钟来处理。
INSERT
事务被回滚怎么办?该主键值将不存在,因此实际记录数将比最大值少一。
count(*)
,则此答案的修改可能比 count(*)
快得多:每天跟踪最后一个自动索引及其相应的计数,然后要求过去的记录计数。还可以处理 delete
,如果在删除时添加一个触发器以减少 previous 总数,如果删除记录 id <= 最后一个自动索引。
对于 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
select rows from sysindexes
where id = Object_ID('TableName') and indid <2
在对我来说非常大的桌子上,
SELECT COUNT(1) FROM TableLarge
需要 37 秒,而
SELECT COUNT_BIG(1) FROM TableLarge
需要 4 秒。
在某个列上放置索引。这应该允许优化器对索引块执行完全扫描,而不是对表进行完全扫描。这将大大降低您的 IO 成本。看前后的执行计划。然后双向测量挂钟时间。
如果您使用的是 Oracle,这个怎么样(假设表统计信息已更新):
select <TABLE_NAME>, num_rows, last_analyzed from user_tables
last_analyzed 将显示上次收集统计数据的时间。
使用 PostgreSQL:
SELECT reltuples AS approximate_row_count FROM pg_class WHERE relname = 'table_name'
对于 SQL Server 2019,您可以使用 APPROX_COUNT_DISTINCT,它:
返回组中唯一非空值的近似数量
并从文档中:
APPROX_COUNT_DISTINCT 专为在大数据场景中使用而设计,并针对以下情况进行了优化: 访问数百万行或更多行的数据集以及 聚合具有许多不同值的一列或多列
还有,函数
实施保证在 97% 的概率内高达 2% 的错误率
比详尽的 COUNT DISTINCT 操作需要更少的内存
与精确的 COUNT DISTINCT 操作相比,较小的内存占用不太可能将内存溢出到磁盘。
实现其 HyperLogLog 背后的算法。
使用 COUNT_BIG()
获取超大文件中的记录数。
SELECT COUNT_BIG(*) FROM TABLENAME;
在 SQL Server 2016 中,我可以检查表属性,然后选择“存储”选项卡 - 这给了我行数、表使用的磁盘空间、使用的索引空间等。
database vendor independent solution
。这也需要一个 GUI,不能自动化。它也不像 COUNT(*) 那样快
也许有点晚了,但这可能有助于其他人的 MSSQL
;WITH RecordCount AS ( SELECT ROW_NUMBER() OVER (ORDER BY
COLUMN_NAME) AS [RowNumber] FROM TABLE_NAME ) SELECT
MAX(RowNumber) FROM RecordCount
COUNT(*) = COUNT(key)
。这是错误的。如果没有NOT NULL
约束 - 那么它们可以不相等(在结果和执行计划中)。index_id < 2
的用途吗?