在PostgreSQL中发现表的行数的快速方法


107

我需要知道表中的行数以计算百分比。如果总计数大于某个预定义常数,我将使用该常数值。否则,我将使用实际的行数。

我可以用SELECT count(*) FROM table。但是,如果我的常量值为500,000,并且表中有5,000,000,000行,则对所有行进行计数将浪费大量时间。

一旦超过我的恒定值,是否可以停止计数?

我只需要确切的行数,只要它低于给定的限制即可。否则,如果计数超出限制,我将改用限制值,并希望尽快给出答案。

像这样:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;

5
您是否只是尝试选择前n行,其中n = 常数+ 1?如果返回的值大于常量,您就知道应该使用常量,如果不是,那您就好了吗?
gddc 2011年

您是否在表格中有一个标识或自动递增字段
Sparky

1
@Sparky:不能保证序列支持的PK是连续的,可以删除行,或者由于异常中止的事务而导致间隙。
亩太短了,

您的更新似乎与您的原始问题矛盾……您是否需要知道确切的行数,或者仅在行数低于阈值时才需要知道确切的行数?
疯狂

1
@RenatoDinhaniConceição:您能解释您要解决的确切问题吗?我认为下面的答案可以解决您最初所说的问题。此更新使它看起来像您想要count(*)以及许多其他字段。如果您可以确切解释您要做什么,这将有所帮助。谢谢。
里特什

Answers:


224

在PostgreSQL中,对表中的行进行计数是很慢的。为了获得准确的数字,由于MVCC的性质,它必须对行进行完整计数。有一种方法来大大加快这如果计数也没有必须要确切喜欢它似乎是在你的情况。

而不是获得确切的计数(大表比较):

SELECT count(*) AS exact_count FROM myschema.mytable;

您会得到如下估算值(非常快):

SELECT reltuples::bigint AS estimate FROM pg_class where relname='mytable';

估算的接近程度取决于您是否运行ANALYZE足够。通常很近。
请参阅PostgreSQL Wiki FAQ
用于count(*)性能的专用Wiki页面

更好了

PostgreSQL的维基文章一个有点草率。它忽略了在一个数据库中以不同的模式存在多个同名表的可能性。要说明这一点:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema'

还是更好

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

更快,更简单,更安全,更优雅。请参阅“ 对象标识符类型 ”手册。

to_regclass('myschema.mytable')在Postgres 9.4+中使用可避免无效表名的例外情况:


TABLESAMPLE SYSTEM (n) 在Postgres 9.5+

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

就像@a_horse commented一样,SELECT如果pg_class由于某些原因当前的统计信息不足,则为该命令新添加的子句可能很有用。例如:

  • 没有autovacuum运行。
  • INSERTDELETE
  • TEMPORARY表格(未被涵盖autovacuum)。

这只会查看随机选择的n%(1在示例中)的块并计算其中的行。选择更大的样本会增加成本并减少错误。准确性取决于更多因素:

  • 行大小分布。如果给定的块恰好比平常的行宽,则计数比平常的低,等等。
  • 死元组或FILLFACTOR每个块占用空间。如果整个表分布不均,则估计值可能会不正确。
  • 一般舍入错误。

在大多数情况下,来自的估计pg_class会更快,更准确。

回答实际问题

首先,我需要知道该表中的行数,如果总计数大于某个预定义常量,

以及是否...

...在计数超过我的恒定值时是可能的,它将停止计数(而不是等待完成计数以告知行计数更大)。

是。您可以将子查询与结合使用LIMIT

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Postgres 实际上停止计数超过给定的限制,您将获得最多n行(在本示例中为500000)的准确和当前计数,否则为n。但是,速度不及中的估算速度。pg_class


8
我最终使用改进的查询更新了Postgres Wiki页面。
Erwin Brandstetter,2013年

5
如果使用9.5,则可以使用以下tablesample子句快速进行估算:例如select count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name 2015年

1
@JeffWidman:由于各种原因,所有这些估计可能大于实际的行数。尤其重要的是,在此期间可能发生了删除。
Erwin Brandstetter

2
@ErwinBrandstetter意识到这个问题很旧,但是如果将查询包装在子查询中,那么限制会仍然有效,还是将整个子查询执行,然后限制在外部查询中。 SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(我问,因为我正试图从可能已经有限制子句的任意查询中获取计数)
Nicholas Erdenberger

1
@NicholasErdenberger:这取决于子查询。无论如何,Postgres可能需要考虑的行数超过了限制(例如,ORDER BY something在不能使用索引的情况下,或者在使用聚合函数时)。除此之外,仅处理子查询中有限数量的行。
Erwin Brandstetter

12

我通过运行在postgres应用程序中执行了一次:

EXPLAIN SELECT * FROM foo;

然后使用正则表达式或类似的逻辑检查输出。对于简单的SELECT *,输出的第一行应如下所示:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

您可以将该rows=(\d+)值用作将要返回的行数的粗略估计,然后仅SELECT COUNT(*)在估计值小于阈值的1.5倍(或您认为对应用程序有意义的任何数目)时才进行实际计算。

根据查询的复杂程度,此数字可能会越来越少。实际上,在我的应用程序中,当我们添加联接和复杂条件时,它变得如此不准确,以至于完全不值钱,甚至不知道在100的幂内如何返回了多少行,因此我们不得不放弃该策略。

但是,如果您的查询足够简单,Pg可以在合理的误差范围内预测它将返回多少行,那么它可能对您有用。


2

引用来自此Blog。

您可以在下面使用查询来查询行数。

使用pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

使用pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;

请注意,您需要对表进行VACUUM分析,此方法才能正常工作。
威廉·阿布玛

1

在Oracle中,您可以rownum用来限制返回的行数。我猜想其他SQL中也存在类似的构造。因此,对于您给出的示例,可以将返回的行数限制为500001,count(*)然后应用a :

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)

1
SELECT count(*)cnt FROM表将始终返回单行。不确定LIMIT如何在此处增加任何收益。
克里斯·贝德纳尔斯基

@ChrisBednarski:我在Oracle数据库上验证了答案的Oracle版本。它的效果很好,可以解决我认为是OP的问题(count(*)使用rownum时为0.05 s,不使用rownum时为1 s)。是的,SELECT count(*) cnt FROM table总是返回1行,但是在LIMIT条件下,当表的大小超过500000时它将返回“ 500001”,而当表的大小<= 500000时将返回“ <size>”。–
Ritesh

2
您的PostgreSQL查询完全是胡说八道。在语法和逻辑上都是错误的。请更正或删除它。
Erwin Brandstetter,

@ErwinBrandstetter:已删除,但没有意识到PostgreSQL是如此不同。
Ritesh

@allrite:毫无疑问,您的Oracle查询工作正常。LIMIT的工作原理有所不同。从根本上讲,它限制了返回给客户端的行数,而不是数据库引擎查询的行数。
克里斯·贝德纳尔斯基

0

文字栏有多宽?

使用GROUP BY,您可以做很多事情来避免数据扫描(至少是索引扫描)。

我建议:

  1. 如果可能,请更改架构以删除文本数据重复项。这样,计数将在“许多”表中的狭窄外键字段上进行。

  2. 或者,使用文本的HASH创建一个生成的列,然后使用哈希列GROUP BY。同样,这是为了减少工作量(通过窄列索引扫描)

编辑:

您最初的问题与您的修改不太吻合。我不确定您是否知道COUNT与GROUP BY一起使用时,将返回每个组的项目数,而不是整个表中的项目数。


0

您可以通过以下查询获得计数(不带*或任何列名)。

select from table_name;

2
这似乎没有比快count(*)
阳光

-3

对于SQL Server(2005或更高版本),一种快速可靠的方法是:

SELECT SUM (row_count)
FROM sys.dm_db_partition_stats
WHERE object_id=OBJECT_ID('MyTableName')   
AND (index_id=0 or index_id=1);

有关sys.dm_db_partition_stats的详细信息在MSDN中进行了解释

该查询将添加(可能是)已分区表的所有部分中的行。

index_id = 0是一个无序表(堆),index_id = 1是一个有序表(集群索引)

此处甚至介绍了速度更快(但不可靠)的方法

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.