Performance postgres未对大型表的SELECT COUNT(*)使用索引

Performance postgres未对大型表的SELECT COUNT(*)使用索引,performance,postgresql,indexing,sql-execution-plan,Performance,Postgresql,Indexing,Sql Execution Plan,我有四张桌子;两个用于当前数据,两个用于存档数据。其中一个归档表有数千万行。所有表都有一对窄索引,并且非常相似 鉴于以下问题: SELECT (SELECT COUNT(*) FROM A) UNION SELECT (SELECT COUNT(*) FROM B) UNION SELECT (SELECT COUNT(*) FROM C_LargeTable) UNION SELECT (SELECT COUNT(*) FROM D); A、 B和D执行索引扫描。C_LargeTable使用

我有四张桌子;两个用于当前数据,两个用于存档数据。其中一个归档表有数千万行。所有表都有一对窄索引,并且非常相似

鉴于以下问题:

SELECT (SELECT COUNT(*) FROM A)
UNION SELECT (SELECT COUNT(*) FROM B)
UNION SELECT (SELECT COUNT(*) FROM C_LargeTable)
UNION SELECT (SELECT COUNT(*) FROM D);
A、 B和D执行索引扫描。C_LargeTable使用seq扫描,执行查询大约需要20秒。表D也有数百万行,但只有C_LargeTable大小的10%左右

然后,如果我修改查询以使用以下逻辑执行,这将充分缩小计数,我仍然会得到相同的结果,使用索引,查询大约需要5秒,或1/4的时间

...
SELECT (SELECT COUNT(*) FROM C_LargeTable WHERE idx_col < 'G') 
       + (SELECT COUNT(*) FROM C_LargeTable WHERE idx_col BETWEEN 'G' AND 'Q')
       + (SELECT COUNT(*) FROM C_LargeTable WHERE idx_col > 'Q')
...
如果存在非常好的索引,并且有一个确保唯一性的覆盖主键,那么对计数进行完整表扫描的I/O开销对我来说是没有意义的。我对postgres的理解是,主键与SQL Server群集索引不同,因为它决定了排序,但它隐式地创建了一个btree索引以确保唯一性,我认为这应该比完整表扫描需要更少的I/O


这是否意味着我可能需要执行优化来组织C_LargeTable中的数据

主键上没有覆盖索引,因为PostgreSQL在9.4之前都不支持它们


由于以下原因,需要进行堆扫描。索引不包含可见性信息。Pg可以执行索引扫描,但它仍然必须检查堆中的可见性信息,并且使用随机I/O的索引扫描来读取整个表,因此seqscan会快得多

确保您运行的是9.2或更高版本,并且已经准备好了。然后,您应该能够在使用可见性贴图的位置执行仅索引扫描。正如马所说,这只在有限的情况下起作用;继续看下去。如果你不让autovacuum定期运行,那么它将过时,Pg将无法进行仅索引扫描


将来,请确保您发布解释,或者最好是解释分析带有任何查询的输出。

您使用的是哪个版本的Postgres?只有9.2及更高版本才能使用索引。这也是一个常见问题:我正在运行9.3。你的回答解释了为什么会发生这种情况。有没有让它做其他事情的建议?我尝试设置enable_seqcan=false,但似乎没有产生太大的性能差异,后来我将其设置回true。您可能还想阅读:EXPLAIN ANALYZE output for the query with and not enable_seqscan=false,请阅读。另外,在对工作台运行真空分析后,是否会发生变化?请始终在您的问题中包含选择版本,并参阅以获得更好问题的指导。感谢您对我的耐心。我执行了真空分析C_LargeTable。解释分析结果随后将存档表D更改为现在也执行seq scan。然后,我执行了VACUUM ANALYZE D并再次执行了查询,它返回到Index Only scann,因为该索引不包含可见性信息。这很有道理!话虽如此,有没有一种方法可以在不关心数据是否已提交的情况下执行计数?@Alan没有,因为它不仅是未提交的数据,还包括已删除的行、更新行的旧版本等。如果autovacuum运行得足够频繁,您可以从pg_统计中获得近似值;请参阅上面count wiki页面的链接。为了澄清,在我勾选此答案之前,我遇到的问题是postgresql设计的结果。如果我真的需要执行快速计数,那么我假设将这些数据保存到其他地方并更新计数是标准做法,例如,增加插入行触发器上的保留值,并使用删除触发器递减?@Alan这是正确的-但是,这样做会对并发插入和/或删除的性能产生可怕的影响,因为它们都是在行计数锁上序列化的。快速计数、良好的插入/删除并发性和适当的事务隔离/可见性非常困难;这是两种选择中的一种。Pg在这方面的表现肯定不是很好。