在PostgreSQL中,计算大表的行数被认为是缓慢的。MVCC模型需要对存活行进行完整的计数以得到精确的数字。如果计数不需要像您的情况一样精确,有一些解决方法可以极大地加快速度。
(请记住,即使“精确”计数在并发写入负载下也有可能失败。)
精确计数
对于大表来说是缓慢的。
在并发写入操作中,它可能在获取时就已经过时了。
SELECT count(*) AS exact_count FROM myschema.mytable;
估计
极其快速:
SELECT reltuples AS estimate FROM pg_class where relname = 'mytable';
通常,估计值非常接近。有多接近,取决于是否运行了足够的ANALYZE
或VACUUM
- 其中“足够”由表的写入活动水平定义。
更安全的估算
上述方法忽略了一个可能性,即一个数据库中具有相同名称但位于不同模式下的多个表。为了解决这个问题:
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';
将real
数转换为bigint
格式可以很好地格式化数字,尤其是对于大数。
更好的估计
SELECT reltuples::bigint AS estimate
FROM pg_class
WHERE oid = 'myschema.mytable'::regclass;
更快、更简单、更安全、更优雅。请参阅对象标识符类型的手册。
在Postgres 9.4+中,用to_regclass('myschema.mytable')
替换'myschema.mytable'::regclass
,可以得到无效表名的情况下不会抛出异常。请参见:
更好的估计(成本很低)
这对于分区表是无效的,因为父表的relpages
始终为-1(而reltuples
包含涵盖所有分区的实际估计值)- 在Postgres 14中测试过。
您必须将所有分区的估计值相加。
我们可以做Postgres计划器所做的事情。引用手册中的行估计示例:
这些数字是根据表上最后一次的
VACUUM
或
ANALYZE
而确定的。规划器会获取表中当前实际的页面数(这是一个廉价的操作,不需要进行表扫描)。如果实际页面数与
relpages
不同,则按比例缩放
reltuples
以得出当前行数估计值。
Postgres使用
src/backend/utils/adt/plancat.c
中定义的
estimate_rel_size
,该函数还涵盖了没有数据在
pg_class
中的特殊情况,因为关系从未被清理过。我们可以在SQL中执行类似的操作:
最简形式
SELECT (reltuples / relpages * (pg_relation_size(oid) / 8192))::bigint
FROM pg_class
WHERE oid = 'mytable'::regclass;
安全且明确
SELECT (CASE WHEN c.reltuples < 0 THEN NULL
WHEN c.relpages = 0 THEN float8 '0'
ELSE c.reltuples / c.relpages END
* (pg_catalog.pg_relation_size(c.oid)
/ pg_catalog.current_setting('block_size')::int)
)::bigint
FROM pg_catalog.pg_class c
WHERE c.oid = 'myschema.mytable'::regclass;
不会因为空表或从未使用
VACUUM
或
ANALYZE
的表而中断。
关于pg_class
的手册:
如果该表尚未进行过清理或分析,则reltuples
包含-1
,表示未知行数。
如果此查询返回NULL
,请运行ANALYZE
或VACUUM
以进行表重复操作。(或者,您可以像Postgres那样基于列类型估算行宽度,但这很繁琐且容易出错。)
如果此查询返回0
,则该表似乎为空。但我建议运行ANALYZE
以确保。(也许要检查您的autovacuum
设置。)
通常,block_size
为8192。current_setting('block_size')::int
涵盖了罕见的例外情况。
表和模式限定使其免受任何search_path
和范围的影响。
无论如何,对我来说,该查询始终需要小于0.1毫秒。
更多网络资源:
SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);
像@a_horse评论的那样,对于SELECT
命令添加的子句可能在某些情况下非常有用,例如:
- 没有运行
autovacuum
。
- 在大型
INSERT
/UPDATE
/DELETE
之后立即执行。
TEMPORARY
表(不受autovacuum
覆盖)。
这只查看随机选择的一个n%(在示例中为1
)块,并计算其中的行数。更大的样本会增加成本并减少误差,你可以自己选择。准确性取决于更多因素:
- 行大小的分布。如果给定的块恰好包含比通常更宽的行,则计数低于通常值等。
- 死元组或
FILLFACTOR
占据每个块的空间。如果在整个表中分布不均,则估计可能有误。
- 一般的舍入误差。
通常,从pg_class
得出的估计值将更快且更准确。
实际问题的答案
首先,我需要知道该表中的行数,如果总计数大于某个预定义常量,
以及它是否......
如果计数超过我的常量值,它将停止计数(而不是等待计数完成以通知行计数大于此值)。
是的。 您可以使用带有 LIMIT
的子查询:
SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;
Postgres实际上会在给定的限制之外停止计数,你可以得到最多n行(例如500000)的确切和当前的计数,否则只能得到n。然而,这种方法不如pg_class中的估算快。