使用ORDER和LIMIT子句进行极慢的PostgreSQL查询

时间:2011-05-17 22:20:25

标签: sql postgresql query-optimization sql-order-by limit

我有一张桌子,我们称之为“foos”,其中有近600万条记录。我正在运行以下查询:

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在查询规划中做出了一些糟糕的选择。任何人对如何解决这个问题都有任何想法?

如果它有帮助,这里是所有3个案例的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)

4 个答案:

答案 0 :(得分:15)

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

另一方面,我会说问题是PG没有理解各种ID的联合分布,这就是为什么该计划如此次优。

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

答案 1 :(得分:2)

可能发生这种情况是因为在尝试订购然后选择之前。为什么不尝试在外部选择all中对结果进行排序?就像是: SELECT * FROM(SELECT ... INNER JOIN ETC ...)ORDER BY ... DESC

答案 2 :(得分:2)

您的查询计划表示

上的过滤器
(((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))

没有出现在SELECT中 - 它来自哪里?

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

答案 3 :(得分: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;