【问题标题】:postgres not using index on SELECT COUNT(*) for a large tablepostgres 未对大表使用 SELECT COUNT(*) 上的索引
【发布时间】:2013-12-21 14:37:55
【问题描述】:

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

鉴于以下查询:

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 scan,查询大约需要 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 的理解是,PRIMARY KEY 不像 SQL Server 集群索引,因为它确定了排序,但它隐式创建了一个 btree 索引以确保唯一性,我认为这需要的 I/O 比完整的要少得多表扫描。

这是否可能表明我可能需要执行优化以在 C_LargeTable 中组织数据?

【问题讨论】:

  • 您使用的是哪个版本的 Postgres?只有 9.2 及更高版本才能使用索引。这也是一个常见问题解答:wiki.postgresql.org/wiki/…
  • 我正在运行 9.3。您的回复解释了为什么会发生这种情况。有没有建议让它做其他事情?我尝试设置 enable_seqcan=false,但它似乎并没有产生太大的性能差异(后来我将其设置回 true)。
  • EXPLAIN ANALYZE 查询的输出有和没有enable_seqscan = false 请。另外,在桌子上运行VACUUM ANALYZE 后它会改变吗?请始终在您的问题中包含SELECT version(),并查看stackoverflow.com/tags/postgresql/info 以获得更好问题的指导。
  • 感谢您对我的耐心。我执行了 VACUUM ANALYZE C_LargeTable。 EXPLAIN ANALYZE 结果将归档表 D 更改为现在也执行 seq 扫描。然后我执行VACUUM ANALYZE D并再次执行查询并返回到Index Only Scan

标签: performance postgresql indexing sql-execution-plan


【解决方案1】:

主键上没有覆盖索引,因为 PostgreSQL 不支持它们(在 9.4 之前是真的,包括 9.4)。

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

确保您运行的是 9.2 或更高版本,并且桌面上的 autovacuumconfigured to run frequently。然后,您应该能够在使用可见性地图的情况下进行仅索引扫描。正如马所指出的,这只在有限的情况下有效;见the wiki page on countindex-only scans。如果您没有让 autovacuum 足够定期地运行,visibility map 将会过时并且 Pg 将无法进行仅索引扫描。

以后,请确保您发布 explain 或最好是 explain analyze 的任何查询输出。

【讨论】:

  • "索引不包含可见性信息。"这很有意义!话虽如此,有没有办法在不关心数据是否提交的情况下执行计数?
  • @Alan 不,没有,因为它不仅仅是未提交的数据 - 它还删除了行、更新行的旧版本等。如果您的 autovacuum 经常运行,您可以从 pg_statistic 获得近似值不过够了;请参阅上面的 count wiki 页面的链接。
  • 为了澄清,在我检查这个答案之前,我遇到的问题是 postgresql 设计的结果。如果我确实需要执行快速计数,那么我假设将此类数据保存在其他地方并更新该计数是标准做法,例如,增加 INSERT 行触发器上的持久值并使用 DELETE 触发器减少?
  • @Alan 这是正确的 - 但是,这样做会对并发 INSERTs 和/或 DELETEs 的性能产生可怕的影响,因为它们都在您的行计数锁上序列化。很难拥有快速计数、良好的插入/删除并发性和适当的事务隔离/可见性;这是“选择两个”类型的东西之一。 Pg 在这方面的表现当然不是很好。
猜你喜欢
  • 1970-01-01
  • 2021-12-21
  • 1970-01-01
  • 2015-05-27
  • 2016-10-26
  • 1970-01-01
  • 1970-01-01
  • 2017-10-28
  • 1970-01-01
相关资源
最近更新 更多