我们的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万行。
答案 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