我在优化查询方面遇到了一些麻烦,并希望此处的某些人能够提供一些指示。
我有两张桌子:
CREATE TABLE "blog_cached_posts" (
"id" int4 NOT NULL DEFAULT nextval('blog_cached_posts_id_seq'::regclass),
"title" varchar(255),
"content" text,
"content_encoded" text,
"published_at" timestamp(6) NULL,
"written_by" varchar(255),
"link" varchar(255),
"blog_id" int4,
"guid" varchar(255),
"created_at" timestamp(6) NULL,
"updated_at" timestamp(6) NULL,
"is_highlighted_post" bool DEFAULT false
)
使用blog_cached_posts.blog_id上的索引
CREATE TABLE "blogs" (
"id" int4 NOT NULL DEFAULT nextval('blogs_id_seq'::regclass),
"site_id" int4,
"image_id" int4,
"name" varchar(255),
"description" text,
"url" varchar(255),
"rss_feed_url" varchar(255),
"active" bool DEFAULT true,
"created_at" timestamp(6) NULL,
"updated_at" timestamp(6) NULL,
"date_highlighted" date,
"highlighted_category_feed_url" varchar(255),
"position" int4
)
使用blogs.site_id上的索引
这是查询:
SELECT "blog_cached_posts".*
FROM "blog_cached_posts"
join blogs on blogs.id = blog_cached_posts.blog_id
WHERE ((published_at IS NOT NULL) AND (blogs.site_id = 80))
ORDER BY published_at desc
LIMIT 5
这是一个EXPLAIN ANALYZE:
Limit (cost=9499.16..9499.17 rows=5 width=1853) (actual time=118.538..118.539 rows=5 loops=1)
-> Sort (cost=9499.16..9626.31 rows=50861 width=1853) (actual time=118.536..118.537 rows=5 loops=1)
Sort Key: blog_cached_posts.published_at
Sort Method: top-N heapsort Memory: 33kB
-> Hash Join (cost=16.25..8654.38 rows=50861 width=1853) (actual time=0.186..82.910 rows=48462 loops=1)
Hash Cond: (blog_cached_posts.blog_id = blogs.id)
-> Seq Scan on blog_cached_posts (cost=0.00..7930.94 rows=52954 width=1853) (actual time=0.042..56.635 rows=52950 loops=1)
Filter: (published_at IS NOT NULL)
-> Hash (cost=13.21..13.21 rows=243 width=4) (actual time=0.135..0.135 rows=243 loops=1)
-> Seq Scan on blogs (cost=0.00..13.21 rows=243 width=4) (actual time=0.007..0.089 rows=243 loops=1)
Filter: (site_id = 80)
Total runtime: 118.591 ms
有没有办法优化它超过目前正在服用的~120ms?
修改
这是我最终做的事情。 (阅读@ypercube评论后)
我为blog_cached_posts添加了一个索引:
CREATE INDEX \"blog_cached_posts_published_at\" ON \"public\".\"blog_cached_posts\" USING btree(published_at DESC NULLS LAST);
COMMENT ON INDEX \"public\".\"blog_cached_posts_published_at\" IS NULL;
我将选择更改为以下内容:
SELECT "blog_cached_posts".*
FROM "blog_cached_posts"
join blogs on blogs.id = blog_cached_posts.blog_id
WHERE published_at is not null and blogs.site_id = 80
ORDER BY published_at desc nulls last
LIMIT 5
这使执行时间缩短到~3ms。
以下是新的执行计划:
Limit (cost=0.00..3.85 rows=5 width=1849) (actual time=0.027..0.047 rows=5 loops=1)
-> Nested Loop (cost=0.00..39190.01 rows=50872 width=1849) (actual time=0.026..0.046 rows=5 loops=1)
-> Index Scan using blog_cached_posts_published_at on blog_cached_posts (cost=0.00..24175.16 rows=52965 width=1849) (actual time=0.017..0.023 rows=5 loops=1)
Filter: (published_at IS NOT NULL)
-> Index Scan using blogs_pkey on blogs (cost=0.00..0.27 rows=1 width=4) (actual time=0.003..0.004 rows=1 loops=5)
Index Cond: (blogs.id = blog_cached_posts.blog_id)
Filter: (blogs.site_id = 80)
Total runtime: 0.086 ms
答案 0 :(得分:5)
您的问题是您无法真实地使用索引直接拉出所需的5个帖子。暂时转到index dos and donts。
如果查询特定的博客, (blog_id, published_at)
(在评论中建议)可能会有所帮助,但您在该查询中最具选择性的约束是site_id上的约束 - 即在一个单独的表上。
Seq Scan on blogs (cost=0.00..13.21 rows=243 width=4) (actual time=0.007..0.089 rows=243 loops=1)
Filter: (site_id = 80)
上述意味着你要么没有site_id的索引,要么这个特定的site_id遍布整个地方,而且Postgres会遍历整个表格,因为它无论如何都需要打开它。
这会导致多个博客ID,这些用于使用散列连接检索所有相关帖子。但由于涉及多个博客,最好的PG可以做的是抓住所有适用的帖子,然后对它们进行排序。
即使您要更改它以便直接在IN()
子句中传递博客ID,(blog_id, published_at)
上的索引也不会按顺序生成所需的行。所以它仍然可以获取所有适用博客的所有帖子,并且可以排除混乱。
解决此问题的一种方法是稍微更改您的架构:
CREATE TABLE "blog_cached_posts" (
"id" int4 NOT NULL DEFAULT nextval('blog_cached_posts_id_seq'::regclass),
...
"published_at" timestamp(6) NULL,
"site_id" int4,
"blog_id" int4,
...
)
请注意额外的site_id。这允许随后在(site_id, published_at desc nulls last)
上创建索引并重写您的查询,如:
SELECT "blog_cached_posts".*
FROM "blog_cached_posts"
WHERE site_id = 80
ORDER BY published_at desc nulls last
LIMIT 5
根据评论,另一种方法是使用触发器维护latest_site_posts
表。您最终会得到类似于上述建议的内容,但会使用较小的表(即较少重复的site_id)。
答案 1 :(得分:1)
根据我对Denis回答的评论,以及Thomas Mueller的评论......
需要blog_cached_posts
上的索引来避免对该表进行顺序扫描。通过制作索引封面blog_id, published_at
,查询可以遵循类似于以下的逻辑...
1.过滤blog_id(80)的博客表
2.对于那里的每条记录,加入blog_cached_posts
3.使用blog_id,published_at索引标识所需的记录
4.可能合并索引本身以允许快速排序
因为索引处于对连接有利的状态,所以对于排序来说结果不太好。基于这些原因,您可能会在两个索引中看到值而不是一个(分别在blog_id和published_at上。)
修改强>
在对Denis回答的评论中,您说由于旧版应用,添加列可能会出现问题。我可以想到一些可能有帮助的提示......
尝试使用您需要的字段创建一个新表,并将原始表替换为新表的视图。例如,新表可以包含附加列中的默认值。
或者创建一个映射表,其中包含您需要为查询关联的fiels。然后可以通过触发器维护此映射表。
答案 2 :(得分:1)
正如我在评论中提到的,我首先会尝试在published_at
上添加一个简单的索引。似乎如果没有ORDER BY
和LIMIT 5
子句,查询将非常有效并且存在所有其他所需的索引。
因此,在用于最终排序的字段上添加索引通常非常有效。
正如Dems在答案中解释的那样:
因为索引(
blog_id, published_at
)处于对连接有利的状态,所以对于排序不太好。基于这些理由,您可能会看到两个索引而不是一个索引(分别在 blog_id 和 published_at 上)。
答案 3 :(得分:0)
因为您的最佳搜索谓词是site_id,请先切换您的表格选择顺序,先从blogs
中选择,然后通过索引直接跳转。另外,将published_at not null
放入连接中,以便尽快删除行,如下所示:
SELECT blog_cached_posts.*
FROM blogs
join blog_cached_posts on blogs.id = blog_cached_posts.blog_id AND published_at IS NOT NULL
WHERE blogs.site_id = 80
ORDER BY published_at desc
LIMIT 5
请注意,此解决方案不需要任何新索引,也不会从中受益。
请告知我们此查询在比较中的表现
答案 4 :(得分:-1)
- 使用LIMIT ['它从表中获取有限的数据']
- 不包括blog_cached_posts。*仅使用必需数据e.i. blog_cached_posts.name,blog_cached_posts.email
- 在select语句中避免使用不需要的引号 选择
blog_cached_posts
。* FROMblog_cached_posts