为什么SELECT COUNT(id)这么慢?

时间:2019-07-16 18:30:10

标签: sql postgresql

我有一个具有1M +条记录的表,并且我在列testΣ上有一个BTREE索引,这也是外键:

x

现在我正在跑步:

id      | x
---------------
1       | 209
2       | 210
...
1000000 | 209

这需要250毫秒(非常慢!),这就是我通过SELECT COUNT(id) FROM t WHERE x = 209 所获得的:

EXPLAIN

这是索引:

Fina­lize­ Aggr­egat­e (cos­t=20­993.­19..­2099­3.19­ rows­=1 widt­h=8)­
  ->­; Gath­er (cos­t=20­993.­09..­2099­3.19­ rows­=1 widt­h=8)­
  Work­ers Plan­ned:­ 1
  ->­; Part­ial Aggr­egat­e (cos­t=19­993.­09..­1999­3.09­ rows­=1 widt­h=8)­
  ->­; Para­llel­ Seq Scan­ on t (cos­t=0.­00..­1982­0.19­ rows­=345­789 widt­h=4)­
  Filt­er: (x = 209)

怎么了?

2 个答案:

答案 0 :(得分:3)

问题在于,有很多行符合条件,您算上id。后者意味着PostgreSQL无法使用仅索引扫描,因为它必须从表中获取id。这是因为,与大多数聚合函数一样,count()将仅计算SQL标准指定的NOT NULL的值。

如果id不可为空,并且表最近是VACUUM版(因此可见性图的大多数块都标记为“全部可见”),那么您将更快:

SELECT count(*) FROM t WHERE x = 209;

有关count()的速度的更多思考,请参见my blog

答案 1 :(得分:1)

您的代码估计将返回1,000,000条中的345,­789行。

基于此估计,Postgres将需要读取所有数据页上的记录。我认为,即使使用覆盖索引,Postgres也始终引用数据页,因为它必须检查各种类型的锁和脏数据。

由于需要读取所有页面,Postgres做出合理的假设,即顺序扫描它们比浏览索引要快。

当索引减少需要读取的数据页数时,它们对于过滤很有用。该查询似乎没有这样做。

编辑:

此问题没有真正的解决方案。您可以创建一个由触发器维护的摘要表。这样会增加insert / update / delete的负载,这可能是不希望的。