2011-05-17 199 views
27

我有一张表,我们称它为“foos”,其中有近600万条记录。我正在运行以下查询:使用ORDER和LIMIT子句的极慢PostgreSQL查询

SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
ORDER BY "foos"."id" DESC 
LIMIT 5 OFFSET 0; 

此查询需要很长时间才能运行(Rails在运行时超时)。有问题的所有ID的索引。好奇的是,如果我删除ORDER BY子句或LIMIT子句,它几乎是瞬间运行。

我假设ORDER BYLIMIT的存在使得PostgreSQL在查询计划中做出了一些不好的选择。任何人有任何想法如何解决这个问题?

万一有帮助,这里是EXPLAIN所有三种情况:

//////// Both ORDER and LIMIT 
SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
ORDER BY "foos"."id" DESC 
LIMIT 5 OFFSET 0; 
                QUERY PLAN              
-------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..16663.44 rows=5 width=663) 
    -> Nested Loop (cost=0.00..25355084.05 rows=7608 width=663) 
     Join Filter: (foos.bar_id = bars.id) 
     -> Index Scan Backward using foos_pkey on foos (cost=0.00..11804133.33 rows=4963477 width=663) 
       Filter: (((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text)) 
     -> Materialize (cost=0.00..658.96 rows=182 width=4) 
       -> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4) 
        Index Cond: (baz_id = 13266) 
(8 rows) 

//////// Just LIMIT 
SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
LIMIT 5 OFFSET 0; 
                   QUERY PLAN                
--------------------------------------------------------------------------------------------------------------------------------------- 
Limit (cost=0.00..22.21 rows=5 width=663) 
    -> Nested Loop (cost=0.00..33788.21 rows=7608 width=663) 
     -> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4) 
       Index Cond: (baz_id = 13266) 
     -> Index Scan using index_foos_on_bar_id on foos (cost=0.00..181.51 rows=42 width=663) 
       Index Cond: (foos.bar_id = bars.id) 
       Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text)) 
(7 rows) 

//////// Just ORDER 
SELECT "foos".* 
FROM "foos" 
INNER JOIN "bars" ON "foos".bar_id = "bars".id 
WHERE (("bars".baz_id = 13266)) 
ORDER BY "foos"."id" DESC; 
                   QUERY PLAN                
--------------------------------------------------------------------------------------------------------------------------------------- 
Sort (cost=36515.17..36534.19 rows=7608 width=663) 
    Sort Key: foos.id 
    -> Nested Loop (cost=0.00..33788.21 rows=7608 width=663) 
     -> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4) 
       Index Cond: (baz_id = 13266) 
     -> Index Scan using index_foos_on_bar_id on foos (cost=0.00..181.51 rows=42 width=663) 
       Index Cond: (foos.bar_id = bars.id) 
       Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text)) 
(8 rows) 
+2

+1有趣的问题。 – 2011-05-17 22:25:43

+0

您的查询与您的查询计划不符。如果您需要帮助,至少要提供完整的相关详细信息... – 2011-05-18 02:11:34

+0

对不同的查询/计划感到抱歉;我试图混淆一下,但回想起来,我不知道为什么。我将在明天更新实际的查询和计划。 – jakeboxer 2011-05-18 05:36:52

回答

1

也许这是因为它试图命令之前,然后进行选择。为什么不尝试在外部选择中对结果进行排序?喜欢的东西: SELECT * FROM(SELECT ... INNER JOIN等)ORDER BY ... DESC

2

您的查询计划表明在

(((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text)) 

一个滤波器,其不出现在选择 - 它从哪里来?

另外,请注意表达式被列为“过滤器”而不是“索引条件”,这似乎表明没有索引应用于它。

+0

对不起。我不知道我为什么要混淆。我会在早上修好它。 – jakeboxer 2011-05-18 05:43:19

13

当你同时拥有LIMIT和ORDER BY时,优化器已经决定通过关键字递减缓冲foo上的未过滤记录,直到它获得其余条件的五个匹配为止的速度更快。在其他情况下,它只是将查询作为嵌套循环运行并返回所有记录。

不好意思,我想说的问题是,PG不赞成联合分布的各种ID,这就是为什么计划是如此次优。

可能的解决方案:我假设你最近运行了ANALYZE。如果没有,那就这样做。这可以解释为什么即使在快速返回的版本上,您的估计时间也很高。如果问题仍然存在,可以运行ORDER BY作为子查询,并在外部查询中使用LIMIT。

+2

很棒的评论,这是我的修复! – Geesu 2013-10-15 12:56:58

+0

好的...所以'foos.bars.last'导致在酒吧上进行完整的索引扫描... nice -_- – Jim 2016-11-04 18:56:43

+1

ok ...因此,只有当foos有0个酒吧时,才会导致完整的索引扫描...仍然烦人,虽然 – Jim 2016-11-04 19:19:02

0

它可能在“foos”上运行全表扫描。你是否尝试更改表的顺序,而是使用左连接而不是内连接,并查看它是否更快地显示结果。

说...

SELECT "bars"."id", "foos".* 
FROM "bars" 
LEFT JOIN "foos" ON "bars"."id" = "foos"."bar_id" 
WHERE "bars"."baz_id" = 13266 
ORDER BY "foos"."id" DESC 
LIMIT 5 OFFSET 0;