2014-01-29 138 views
0

我們的PostgreSQL數據庫包含以下表:PostgreSQL的速度很慢索引掃描

  • 類別

    id SERIAL PRIMARY KEY 
    name TEXT 
    
  • 文章

    id SERIAL PRIMARY KEY 
    content TEXT 
    
  • categories_articles(多對一對多的關係)

    category_id INT REFERENCES categories (id) 
    article_id INT REFERENCES articles (id) 
    UNIQUE (category_id, article_id) 
    
  • 評論

    article_id INT REFERENCES articles (id) 
    posted_date TIMESTAMP NOT NULL 
    is_visible BOOLEAN NOT NULL 
    is_banned BOOLEAN NOT NULL 
    message TEXT 
    

我們對comments表有部分指數:

CREATE INDEX comments_posted_date_idx 
ON comments USING btree (posted_date) 
WHERE is_visible = TRUE AND is_banned = FALSE; 

因此,我們需要通過類來獲得最近的評論:

SELECT * FROM comments co 
JOIN categories_articles ca 
    ON ca.article_id = co.article_id 
WHERE ca.category_id = 1 
    AND co.is_visible = TRUE 
    AND co.is_banned = FALSE 
ORDER BY co.posted_date DESC 
LIMIT 20; 

EXPLAIN ANALYZE輸出:

Limit (cost=0.00..1445.20 rows=20 width=24) (actual time=93969.479..98515.109 rows=20 loops=1) 
    -> Nested Loop (cost=0.00..7577979.47 rows=104871 width=24) (actual time=93969.475..98515.084 rows=20 loops=1) 
     -> Index Scan Backward using comments_posted_date_idx on comments co (cost=0.00..3248957.69 rows=9282514 width=40) (actual time=13.405..82860.852 rows=117881 loops=1) 
     -> Index Scan using categories_articles_article_id_idx on categories_articles ca (cost=0.00..0.45 rows=1 width=16) (actual time=0.132..0.132 rows=0 loops=117881) 
       Index Cond: (article_id = co.article_id) 
       Filter: (category_id = 1) 
Total runtime: 98515.179 ms 

有什麼辦法優化查詢?

UPD:表comments有約1100萬行。

+1

您的統計信息可能爲開啓。嘗試'真空分析評論;'首先。 – wildplasser

+0

這通常是避免*的更好方法。嘗試寫出cols。 – PeterRing

+0

另外,您可能需要在聯結表上創建_reversed_索引:'在categories_article(article_id,category_id)上創建唯一索引'另外:通過正確的統計信息和調優,我期望此查詢產生散列計劃。 – wildplasser

回答

2

這是在沒有很好的修復確實存在一種病態的計劃......總之,選擇找到基本的行是:通過指數posted_date以相反的順序

  • 運行,巢加盟使用article_id,直到找到20個匹配項 - 掃描過程中大部分表格,因爲沒有太多行匹配,因爲它現在正在進行 - 並停止;或

  • 運行例如category_id,嵌套或散列加入article_id以查找所有匹配的註釋,並將top-n排序前20個註釋。

如果您有很多文章,第一篇將會更快。如果你很少,第二將是。麻煩的是,Postgres沒有收集相關的統計數據;它正在做出假設,而不一定是好的。

可能能夠獲得這部分更快的索引掃描:

Index Cond: (article_id = co.article_id) 
    Filter: (category_id = 1) 

通過添加反向(也是唯一的)指數(article_id, category_id)categories_articles表,而不是在普通(article_id) - 你忘記提及你的問題,但仍然出現在你的計劃中。

帶和不帶它周圍,還嘗試在comments表上(article_id, posted_date)(posted_date, article_id)(部分)指標,而不是在普通(posted_date)

+0

謝謝,我意識到除了使數據庫非規範化之外別無選擇。 – user3247938

0

由於EXPLAIN輸出只顯示索引掃描,所以真正的問題是:時間到了哪裏?我會猜測你的磁盤IO是否飽和,你可以通過運行「iostat 1」或類似的工具來驗證,看看百分比繁忙計數器是否爲100%或者(如果沒有這樣的計數器),看看你的「等待「CPU狀態接近100%。

+0

事實並非如此,因爲這是一個沒有任何負載的測試服務器。 – user3247938

+0

索引訪問*是*我正在談論的負載。我仍然認爲你應該通過查看執行查詢期間的IO負載來驗證這個理論。 –

0

(category_id,posted_date)上的索引有什麼問題?我假設你總是有一個你搜索的category_id?

+1

對不起,我不明白你的意思。你建議在'(category_id,posted_date)'上創建一個複合索引嗎?但是這些列在不同的表格中。順便說一句,'category_id'已經被編入索引。 – user3247938

0

在學習查詢計劃時,不應該使用限制。該關鍵字完全改變了查詢計劃器,請參閱:http://www.postgresql.org/docs/9.1/static/queries-limit.html 因此,我不建議您嘗試花時間改進解釋分析。

嘗試用下面的設置來打: work_mem effective_cache_size

你可以試着重寫該查詢擺脫嵌套循環。我會給你幾個例子,哪一個有用,也許沒有,但你有一些想法。

SELECT * 
FROM comments co 
JOIN categories_articles ca 
    ON ca.article_id = co.article_id and ca.category_id = 1 
WHERE co.is_visible = TRUE 
    AND co.is_banned = FALSE 
ORDER BY co.posted_date DESC 

with comments as (
select * -- Better with only THE FIELDS YOU NEED 
from comments 
where co.is_visible = TRUE 
and co.is_banned = FALSE 
) 
select * 
from comments co 
join categories_articles ca 
on ca.article_id = co.article_id 
ORDER BY co.posted_date DESC 
+1

咦?當然,你應該在分析thye查詢時使用限制...否則,你最終會針對完全不同的查詢進行優化(從所運行的行中選擇所有行...)(選擇幾行...... )。 –