Postgres - медленное простое соединение с предложением where- - PullRequest
4 голосов
/ 17 июня 2011

У меня возникли проблемы с оптимизацией запроса, и я надеялся, что кто-то здесь сможет предоставить несколько указателей.

У меня есть две таблицы:

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

Вот ОБЪЯСНИТЕЛЬНЫЙ АНАЛИЗ:

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

Есть ли способ оптимизировать это, помимо ~ 120 мс, которые он в настоящее время принимает?

РЕДАКТИРОВАТЬ

Вот что я в итоге сделал.(После прочтения комментария @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

Этодовел время выполнения до ~ 3 мс.

Вот новый план выполнения:

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 ]

5 голосов
/ 17 июня 2011

Ваша проблема в том, что вы не можете реально использовать индекс, чтобы сразу получить необходимые 5 сообщений.Перейдите к index 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)

Вышесказанное означает, что либо у вас нет индекса site_id, либо этот конкретный site_id повсюду, и Postgres идетчерез всю таблицу, поскольку ее нужно будет открыть независимо от этого.

Это приводит к нескольким идентификаторам блогов, которые используются для получения всех соответствующих сообщений с использованием хеш-соединения.Но так как задействованы несколько блогов, лучшее, что может сделать PG, - это захватить все применимые посты и затем отсортировать их по типу top-n.

Даже если вы измените его так, что вы передадите идентификаторы блога непосредственно в IN() предложение, индекс по (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 голос
/ 20 июня 2011

Как я упоминал в комментарии, я сначала попытался бы добавить простой индекс для published_at. Похоже, что если бы не было предложений ORDER BY и LIMIT 5, запрос был бы достаточно эффективным и все остальные необходимые индексы существовали.

Поэтому добавление индекса в поле, используемое для окончательной сортировки, обычно довольно эффективно.

Как объяснил Демс в своем ответе:

Поскольку индекс (blog_id, published_at) находится в состоянии, которое подходит для объединения, оказывается, что менее хорошо для сортировки . На этом основании вы можете увидеть значение в двух индексах, а не в одном (для blog_id и publ_at отдельно.)

1 голос
/ 17 июня 2011

Согласно моему комментарию на ответ Дениса и комментарию Томаса Мюллера ...

Индекс на blog_cached_posts необходим, чтобы избежать последовательного сканирования этой таблицы.Прикрывая индекс blog_id, published_at, запрос может следовать логике, подобной следующей ...
1. Отфильтруйте таблицу блогов для site_id (80)
2. Для каждой записи там присоединитесь кblog_cached_posts
3. Используйте индекс blog_id, publ_at для определения необходимых записей
4. Возможно объединить этот индекс с самим собой, чтобы обеспечить быструю сортировку

Поскольку индекс находится в состоянии, которое подходит длясоединение оказывается менее подходящим для такого рода.На этом основании вы можете увидеть значение в двух индексах, а не в одном (для blog_id и publ_at отдельно.)


EDIT

В комментарии к ответу Дениса вы говорите, что добавление столбца может оказаться проблематичным из-за устаревших приложений.Я могу вспомнить пару советов, которые могут помочь в этом ...

Попробуйте создать новую таблицу с нужными вам полями и заменить исходную таблицу видом на новую таблицу.Новая таблица может иметь, например, значения по умолчанию в дополнительных столбцах.

Или создать таблицу сопоставления с полями, которые необходимо связать вместе для запроса.Затем эту таблицу сопоставления можно поддерживать с помощью триггера.

0 голосов
/ 17 июня 2011

Поскольку ваш лучший предикат поиска - 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

Обратите внимание, что это решение не требует каких-либо новых индексов и не будет их использовать.

Пожалуйста, дайте нам знать, как этот запрос выполняет в сравнении

0 голосов
/ 17 июня 2011
  • использовать LIMIT ['извлечение ограниченных данных из таблицы'] *
  • Insified of blog_cached_posts. * Использовать только обязательные данные ei blog_cached_posts.name, blog_cached_posts.email
  • Избегайте нежелательных "" кавычек в операторе выбора SELECT blog_cached_posts. * FROM blog_cached_posts
...