як використовувати індекс для прискорення сортування в постгресах


10

Я використовую постгреси 9.4.

Схема messagesмає таку схему: повідомлення належать до feed_id, а також розмістив post_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може бути нульовим, всі повідомлення з null 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 Fiddle я визначив схему, точний запит та очікуваний результат.

Але ефективність запиту відбувається повільно, коли таблиця повідомлень стає великою. Я спробував додати декілька індексів сортування, але, схоже, не використовується індекс. Ось пояснення: http://explain.depesz.com/s/Sv2

Як я можу створити правильний індекс?


На перший погляд, ORDER BYв підзапиті абсолютно марно. Крім того, пов'язаний план не може бути результатом розміщеного запиту - наприклад, немає жодної згадки metadata.
дезсо

Ваше опис не поширюється на роль feed_idі , posted_atі ви не згадували metadataвзагалі, що , як видається, типу JSON? Будь ласка, відремонтуйте своє запитання, щоб воно було послідовним. Ви вибираєте> 500k рядків у CTE ... Скільки рядків у таблиці? Який відсоток рядків ви обираєте зазвичай в CTE? Який відсоток рядків має parent_id IS NULL? Розгляньте інформацію у тезі [postgresql-performance] для питань щодо ефективності.
Ервін Брандстеттер

Також важливо: скільки рядків для кожного parent_id? (хв. / сер / макс.)
Ервін Брандштеттер

Вибачте, я намагався зробити це питання більш чітким, зменшивши деякі стовпці, share_count насправді знаходився в hstore metadata. В даний час таблиця повідомлень містить 10 мільйонів даних, але швидко збільшується. Я думаю, щоб розділити таблиці розділів для кожного feed_id. Оскільки я отримую лише один ідентифікатор каналу. відсоток parent_id null vs 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може бути, а може і не бути найкращою технікою запитів для цієї мети. Прочитайте детальне пояснення тут:

І, можливо, більш швидкі альтернативи тут:

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.