как использовать индекс для ускорения сортировки в postgres

10

Я использую Postgres 9.4.

Схема messagesимеет следующую схему: сообщения принадлежат feed_id, и имеет posts_at, также сообщения могут иметь родительское сообщение (в случае ответов).

                    Table "public.messages"
            Column            |            Type             | Modifiers
------------------------------+-----------------------------+-----------
 message_id                   | character varying(255)      | not null
 feed_id                      | integer                     |
 parent_id                    | character varying(255)      |
 posted_at                    | timestamp without time zone |
 share_count                  | integer                     |
Indexes:
    "messages_pkey" PRIMARY KEY, btree (message_id)
    "index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)

Я хочу вернуть все заказанные сообщения share_count, но для каждого parent_idя хочу вернуть только одно сообщение. т. е. если несколько сообщений имеют одинаковое значение parent_id, posted_atвозвращается только последнее ( ). Значение parent_idможет быть нулевым, все сообщения с нулевым значением parent_idдолжны возвращаться.

Запрос, который я использовал:

WITH filtered_messages AS (SELECT * 
                           FROM messages
                           WHERE feed_id IN (7) 
                           AND (posted_at >= '2015-01-01 04:00:00.000000') 
                           AND (posted_at < '2015-04-28 04:00:00.000000'))
    SELECT *
    FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
                          message_id, 
                          posted_at, 
                          share_count
          FROM filtered_messages
          ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
         ) messages
    ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;

Вот http://sqlfiddle.com/#!15/588e5/1/0 , в скрипте SQL я определил схему, точный запрос и ожидаемый результат.

Но производительность запроса замедляется, как только таблица сообщений становится большой. Я пытался добавить несколько индексов сортировки, но он, похоже, не использует индекс. Вот объяснение: http://explain.depesz.com/s/Sv2

Как я могу создать правильный индекс?

Чжаохан Вэн
источник
На первый взгляд, ORDER BYподзапрос совершенно бесполезен. Кроме того, связанный план не может быть результатом опубликованного запроса - например, нет упоминания о нем metadata.
Дезсо,
Ваше описание не распространяется на роль feed_idи , posted_atи вы не упоминали metadataвообще, что , как представляется, типа JSON? Пожалуйста, исправьте свой вопрос, чтобы сделать его последовательным. Вы выбираете> 500 тыс. Строк в CTE ... Сколько строк в таблице? Какой процент строк вы обычно выбираете в CTE? Какой процент строк имеет parent_id IS NULL? Посмотрите информацию в теге [postgresql-performance] для вопросов производительности.
Эрвин Брандстеттер
Также важно: сколько строк для каждого parent_id? (мин. / ср. / макс.)
Эрвин Брандштеттер,
извините, я пытался прояснить вопрос, уменьшив некоторые столбцы, на самом деле share_count была в hstore metadata. В настоящее время таблица сообщений содержит 10 мил данных, но быстро увеличивается. Я думаю, чтобы разделить на таблицы разделов для каждого feed_id. Так как я получаю только по идентификатору канала. процентное значение parent_id NULL против NULL составляет около 60% / 40%. типичная выборка составляет около 1-2% таблицы. (около 100K сообщений) Производительность для 100K составляет около 1 с, но когда он достигает 500K +, он использует индекс растрового изображения и обычно занимает 10 с.
Чжаохан Вэн

Ответы:

9

запрос

Этот запрос должен быть значительно быстрее в любом случае:

SELECT parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NULL  -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
       parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NOT NULL  -- match index condition
ORDER  BY parent_id, posted_at DESC NULLS LAST
)
ORDER  BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
  • CTE здесь не делает ничего, что не мог бы доставить и простой подзапрос. И CTE вводит барьер оптимизации, поскольку он выполняется отдельно и его результат материализуется.

  • У вас есть еще один уровень подзапроса, чем вам на самом деле нужно.

  • Выражение (COALESCE(parent_id, message_id)несовместимо с простым индексом, вам понадобится индекс для этого выражения. Но это может быть не очень полезным, в зависимости от распределения данных. Следуйте моим ссылкам ниже для получения подробной информации.

  • Разделение простого случая parent_id IS NULLна отдельный SELECTможет дать или не дать оптимальное. Особенно нет, если это все-таки редкий случай, и в этом случае комбинированный запрос с индексом (COALESCE(parent_id, message_id)может работать лучше. Другие соображения применимы ...

индексы

Особенно когда поддерживается с этими индексами:

CREATE INDEX messages_idx_null ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;

CREATE INDEX messages_idx_notnull ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;

Два частичных индекса охватывают всю таблицу вместе и имеют примерно одинаковый размер вместе как один общий индекс.

Последние два столбца parent_id, message_idимеют смысл только в том случае, если вы получаете только сканирование по индексу . Еще удалите их из обоих индексов.

SQL Fiddle.

В зависимости от недостающих деталей, DISTINCT ONможет быть или не быть лучшим методом запроса для этой цели. Прочитайте подробное объяснение здесь:

И, возможно, более быстрые альтернативы здесь:

Эрвин Брандштеттер
источник