Postgres - 使用where子句缓慢简单连接

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)

Den*_*rdy 5

你的问题是你无法现实地使用索引直接拉出所需的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).


ype*_*eᵀᴹ 1

正如我在评论中提到的,我首先尝试在published_at. 看来如果没有ORDER BYandLIMIT 5子句,查询会非常有效,并且所有其他需要的索引都存在。

因此,在用于最终排序的字段上添加索引通常是相当有效的。

正如民主党在回答中所解释的那样:

因为索引 ( blog_id, published_at) 处于有利于连接的状态,但结果却不太利于排序。基于这些理由,您可能会看到两个索引而不是一个索引的价值(分别在blog_idpublished_at上)。