PostgreSQL索引扫描非常慢

时间:2014-01-29 13:37:47

标签: sql postgresql database-design

我们的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有大约1,100万行。

4 个答案:

答案 0 :(得分:2)

这是一个病态的计划,没有好的修复方法确实存在...简而言之,查找行的选项基本上是:

  • 以相反的顺序在posted_date上运行索引,并使用article_id进行嵌套连接直到找到20个匹配项 - 在此过程中扫描表的大部分内容,因为没有那么多行正如它现在所做的那样 - 并且停止;或

  • 运行索引,例如category_id上的article_id,嵌套或散列加入以查找所有匹配的评论,并对前20条评论进行排序。

如果你有很多文章,第一篇文章会更快。如果你很少,那么第二个就是。麻烦的是,Postgres没有收集相关统计数据;它正在做出假设,而不一定是好的假设。

可能能够为此部分获得更快的索引扫描:

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

(article_id, category_id)表格上的categories_articles上添加反向(和唯一)索引,而不是在您的问题中忘记提及的普通(article_id)上,但仍会出现在您的问题中计划。

无论有没有,也可以在(article_id, posted_date)表格上的(posted_date, article_id)comments上尝试(部分)索引,而不是普通(posted_date)

答案 1 :(得分:0)

由于EXPLAIN输出仅显示索引扫描,真正的问题是:时间在哪里?我会做出有根据的猜测你的磁盘IO已经饱和,你可以通过运行“iostat 1”或类似工具验证%busy计数器是100%还是(如果没有这样的计数器)看看你的“等待” “CPU状态接近100%。

答案 2 :(得分:0)

(category_id,posted_date)索引出了什么问题?我假设你总是有一个你搜索的category_id?

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