如何使用有序结果减少postgresql IN查询的索引扫描时间?

时间:2013-06-27 15:15:57

标签: postgresql postgresql-9.2

我构建了一个简单的应用程序,用于使用rails和postgresql阅读RSS提要,但是当我尝试在feed_entries表中查询来自多个提要的帖子时遇到了性能问题。示例查询如下所示,以检索给定的feed ID集合的20个最新条目:

SELECT * FROM feed_entries WHERE feed_id IN (19, 21, 383, 1867, 3103) ORDER BY published_at DESC LIMIT 20;

feed_entries表中有大约400万行,使用Fugu计划在Heroku Postgres上托管,它有一些索引,包括:

"index_feed_entries_on_feed_id_and_published_at" btree (feed_id, published_at)
"index_feed_entries_on_published_at" btree (published_at)

以下是查询规划器的结果:

EXPLAIN ANALYZE SELECT * FROM feed_entries WHERE feed_id IN (19, 21, 383, 1867, 3103) ORDER BY published_at DESC LIMIT 20;

 Limit  (cost=4353.93..4353.94 rows=20 width=1016) (actual time=12172.275..12172.325 rows=20 loops=1)
   ->  Sort  (cost=4353.93..4355.07 rows=2286 width=1016) (actual time=12172.268..12172.284 rows=20 loops=1)
     Sort Key: published_at
     Sort Method: top-N heapsort  Memory: 52kB
     ->  Index Scan using index_feed_entries_on_feed_id_and_published_at on feed_entries  (cost=0.00..4341.76 rows=2286 width=1016) (actual time=8.612..12169.504 rows=630 loops=1)
           Index Cond: (feed_id = ANY ('{19,21,383,1867,3103}'::integer[]))
Total runtime: 12172.520 ms

计划程序看起来正在使用适当的索引,但是扫描索引仍然需要大约12秒,这对于有400万行的表来说太长了。如果我完全按照上面那样重复查询规划器,那么第二次它告诉我整个事情只需要2毫秒,也许这只是因为第一个查询的结果被缓存,但它仍然让我感到困惑。我还尝试在运行查询之前运行VACUUM ANALYZE,但它没什么区别。另外,如果我在表中查询单个feed_id,那么查询规划器使用Index Scan Backward using index_feed_entries_on_feed_id_and_published_at on feed_entries,总执行时间要快得多,大约20ms。

我是否可以采用其他策略来优化这种相对简单的IN查询的性能?

2 个答案:

答案 0 :(得分:1)

要尝试的另一件事是这种替代查询形式:

SELECT * 
FROM   feed_entries
JOIN  (unnest('{19,21,383,1867,3103}'::int[]) AS feed_id) sub USING (feed_id)
ORDER  BY published_at DESC
LIMIT  20;

multi-column indexes中列的排序顺序很重要。使用:

CREATE index_feed_entries_2 ON feed_entries (feed_id, published_at DESC)

如果你根据这个索引CLUSTER你的表,这可能会给你一点点提升,但是随着大量的更新,效率会下降。有关详细信息,请阅读此相关答案的最后一章:
Bitmap Heap Scan performance

当然,所有the usual advice on performance optimization也适用。

答案 1 :(得分:0)

尝试使用DESC订单创建索引。 例如

create index feed_entries_published_at_desc_idx on feed_entries ( published_at desc ) with (fillfactor=100);

你可以在(feed_id,published_at desc)上尝试类似(复合)索引,看看它是如何工作的。