jak używać indeksu, aby przyspieszyć sortowanie w postgresie

10

Używam postgres 9.4.

messagesMa następujący schemat: Komunikaty należący do feed_id i ma posted_at, również komunikaty mogą mieć wiadomość nadrzędnego (w przypadku odpowiedzi).

                    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)

Chcę zwrócić wszystkie wiadomości uporządkowane według share_count, ale dla każdej parent_idchcę zwrócić tylko jedną wiadomość. tzn. jeśli wiele wiadomości ma to samo parent_id, posted_atzwracana jest tylko najnowsza ( ). parent_idMoże być null, null wiadomości parent_idpowinien cały zwrot.

Użyłem zapytania:

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;

Oto http://sqlfiddle.com/#!15/588e5/1/0 , w skrzynce SQL zdefiniowałem schemat, dokładne zapytanie i oczekiwany wynik.

Ale wydajność zapytania jest niska, gdy tabela komunikatów staje się duża. Próbowałem dodać wiele indeksów sortujących, ale wydaje się, że nie używa tego indeksu. Oto wyjaśnienie: http://explain.depesz.com/s/Sv2

Jak mogę utworzyć poprawny indeks?

Zhaohan Weng
źródło
Na pierwszy rzut oka ORDER BYpodzapytanie jest całkowicie bezużyteczne. Ponadto połączony plan nie może być wynikiem wysłanego zapytania - metadatana przykład nie ma wzmianki o nim .
dezso
Twój opis nie obejmuje roli feed_idi posted_atnie wspomniałeś metadatawcale, który wydaje się być typem JSON? Napraw pytanie, aby było spójne. Wybierasz> 500 tys. Wierszy w CTE ... Ile wierszy znajduje się w tabeli? Jaki procent wierszy zazwyczaj wybierasz w CTE? Jaki procent wierszy ma parent_id IS NULL? Rozważ informacje w tagu [postgresql-performance] w przypadku pytań dotyczących wydajności.
Erwin Brandstetter
Ważne również: ile rzędów dla każdego parent_id? (min / avg / max)
Erwin Brandstetter
przepraszam, starałem się wyjaśnić pytanie, zmniejszając niektóre kolumny, share_count faktycznie był w hstore metadata. Obecnie tabela komunikatów zawiera 10 milionów danych, ale szybko rośnie. Myślę, że podzielę się na tabele partycji dla każdego feed_id. Ponieważ pobieram tylko identyfikator kanału. Procent Parent_id null vs not null wynosi około 60% / 40%. typowe pobranie stanowi około 1-2% tabeli. (około 100 000 wiadomości) Wydajność dla 100 000 wynosi około 1 s, ale gdy osiągnie 500 000 +, używa indeksu bitmap i zwykle zajmuje 10 sekund.
Zhaohan Weng

Odpowiedzi:

9

Pytanie

W każdym razie to zapytanie powinno być znacznie szybsze:

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 nie robi tutaj niczego, czego zwykłe podzapytanie nie mogłoby również dostarczyć. CTE wprowadza barierę optymalizacyjną, ponieważ jest wykonywana osobno, a jej wynik jest materializowany.

  • Masz jeszcze jeden poziom podkwerendy, niż potrzebujesz.

  • Wyrażenie (COALESCE(parent_id, message_id)nie jest kompatybilne ze zwykłym indeksem, potrzebny byłby indeks tego wyrażenia. Ale może to również nie być bardzo przydatne, w zależności od dystrybucji danych. Aby uzyskać szczegółowe informacje, skorzystaj z moich linków poniżej.

  • Podział prostego przypadku parent_id IS NULLna osobny SELECTmoże, ale nie musi, zapewnić optymalnego. Zwłaszcza nie, jeśli i tak jest to rzadki przypadek, w którym to przypadku zapytanie łączone z indeksem (COALESCE(parent_id, message_id)może działać lepiej. Obowiązują inne uwagi ...

Wskaźniki

Zwłaszcza gdy są obsługiwane tymi indeksami:

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;

Dwa częściowe indeksy pokrywają razem całą tabelę i są mniej więcej tego samego rozmiaru, co pojedynczy indeks całkowity.

Dwie ostatnie kolumny parent_id, message_idmają sens tylko wtedy, gdy wykonasz z nich skany tylko z indeksu . W przeciwnym razie usuń je z obu indeksów.

SQL Fiddle.

W zależności od brakujących szczegółów DISTINCT ONmoże być najlepsza technika zapytań do tego celu. Przeczytaj szczegółowe wyjaśnienie tutaj:

I być może szybsze alternatywy tutaj:

Erwin Brandstetter
źródło