众所周知,在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';
转换为bigint可以很好地格式化实数,特别是对于大数。
更好地估计
SELECT reltuples::bigint AS estimate
FROM pg_class
WHERE oid = 'myschema.mytable'::regclass;
更快、更简单、更安全、更优雅。参见手册中的对象标识符类型。
取代“myschema。在Postgres 9.4+中使用to_regclass('myschema.mytable')来获取无效表名的异常,而不是什么都没有。看到的:
如何检查一个表是否存在于给定的模式中
更好的估计(对于很少的额外成本)
这对分区表不起作用,因为父表的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; -- your table here
安全且明确
SELECT (CASE WHEN c.reltuples < 0 THEN NULL -- never vacuumed
WHEN c.relpages = 0 THEN float8 '0' -- empty table
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; -- schema-qualified table here
不打破空桌子和桌子从未见过真空或分析。关于pg_class的手册:
如果该表从未被抽真空或分析过,则reltuples包含-1,表示行数未知。
如果此查询返回NULL,则对表运行ANALYZE或VACUUM并重复。(或者,您也可以像Postgres那样基于列类型估计行宽,但这很乏味且容易出错。)
如果这个查询返回0,表似乎是空的。但我会通过分析来确定。(也可以检查一下你的自动吸尘器设置。)
通常,block_size为8192。Current_setting ('block_size')::int覆盖罕见异常。
表和模式限定使它不受任何搜索路径和范围的影响。
无论哪种方式,对我来说,查询所花费的时间始终小于0.1 ms。
更多网络资源:
Postgres Wiki常见问题解答
计数估计和计数(*)性能的Postgres wiki页面
Postgres 9.5+中的TABLESAMPLE SYSTEM (n)
SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);
与@a_horse所注释的一样,如果pg_class中的统计数据由于某种原因不够及时,则为SELECT命令添加的子句可能很有用。例如:
没有自动真空运行。
紧接着在一个大的INSERT / UPDATE / DELETE之后。
临时表(不被autovacuum覆盖)。
这只查看一个随机的n %(在本例中为1)块选择,并计算其中的行数。更大的样本会增加成本,减少误差,这是你的选择。准确性取决于更多因素:
行大小的分布。如果一个给定的块刚好拥有比通常更宽的行,计数就会比通常更低等等。
死元组或FILLFACTOR占用每个块的空间。如果在表中分布不均匀,估计可能会出错。
一般舍入错误。
通常,来自pg_class的估计会更快更准确。
实际问题的答案
首先,我需要知道表中的行数,如果总行数
Count大于某个预定义的常数,
是否……
... 是否有可能此时计数通过我的常数值,它会吗
停止计数(不要等到计数结束才通知
行数更大)。
是的。你可以使用LIMIT子查询:
SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;
Postgres实际上在给定限制之外停止计数,您将获得最多n行的精确和当前计数(在示例中为500000),否则为n。但是没有pg_class中的估计快。