sql - PostgreSQL 非常慢的索引扫描

标签 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 有大约 1100 万行。

最佳答案

这是一个病态的计划,实际上没有很好的解决办法......简而言之,查找行的选项基本上是:

  • 以相反顺序遍历 posted_date 上的索引,并使用 article_id 进行嵌套连接,直到找到 20 个匹配项 — 扫描表中的很大一部分process 因为没有那么多行匹配,就像它现在所做的那样 - 并停止;或者

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

如果您有很多文章,第一个会更快。如果你很少,第二个将是。问题是,Postgres 不收集相关统计数据;它在做假设,但不一定是好的假设。

可能能够对该部分进行更快的索引扫描:

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

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

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

关于sql - PostgreSQL 非常慢的索引扫描,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21432747/

相关文章:

php - 数据库中具有共同属性和独立属性的元素

mysql - 按字符串或整数搜索/索引

mysql - 如何在 MySql 中使用联合查询从每个表中获得额外的列?

sql - VBA 错误 3134 INSERT INTO 语句中的语法错误

sql - MySQL 选择查询,从动态生成的列中选择位置

java - SELECT COUNT(*) FROM (SELECT) AS SubQuery 在 MS ACCESS 中有效,但在 JDBC 中无效

ruby-on-rails - Rails 运行总计专栏

php - 使用 PostgreSQL 的 gnupg 扩展解密时遇到问题

r - 将文件从 PostgreSQL 导入到 R

java - 如何在 Spring Web 应用程序中实现共享功能?