2011-05-17 29 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;