Tho*_*pel 4 sql postgresql optimization performance join
我在优化查询时遇到了一些麻烦,并且希望这里的某个人能够提供一些指示.
我有两张桌子:
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
)
Run Code Online (Sandbox Code Playgroud)
使用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
)
Run Code Online (Sandbox Code Playgroud)
使用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
Run Code Online (Sandbox Code Playgroud)
这是一个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
Run Code Online (Sandbox Code Playgroud)
有没有什么方法可以优化它超过目前正在采用的~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;
Run Code Online (Sandbox Code Playgroud)
我将选择更改为以下内容:
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
Run Code Online (Sandbox Code Playgroud)
这使执行时间缩短到~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
Run Code Online (Sandbox Code Playgroud)
你的问题是你无法现实地使用索引直接拉出所需的5个帖子.跳过索引dos和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)
Run Code Online (Sandbox Code Playgroud)
上面的意思是你在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,
...
)
Run Code Online (Sandbox Code Playgroud)
注意额外的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
Run Code Online (Sandbox Code Playgroud)
根据评论,另一种方法是latest_site_posts使用触发器维护表.您将得到类似于上述建议的内容,但使用较小的表(即较少重复的site_id).
正如我在评论中提到的,我首先尝试在published_at. 看来如果没有ORDER BYandLIMIT 5子句,查询会非常有效,并且所有其他需要的索引都存在。
因此,在用于最终排序的字段上添加索引通常是相当有效的。
正如民主党在回答中所解释的那样:
因为索引 (
blog_id, published_at) 处于有利于连接的状态,但结果却不太利于排序。基于这些理由,您可能会看到两个索引而不是一个索引的价值(分别在blog_id和published_at上)。