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

时间:2011-06-17 12:46:54

标签: sql performance postgresql optimization 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
)

使用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

5 个答案:

答案 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 BYLIMIT 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。* FROM blog_cached_posts
  •