come utilizzare l'indice per velocizzare l'ordinamento in postgres


10

Sto usando Postgres 9.4.

L' messagesha il seguente schema: i messaggi appartiene alla feed_id, e ha posted_at, anche i messaggi possono avere un messaggio principale (in caso di risposte).

                    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)

Voglio restituire tutti i messaggi ordinati per share_count, ma per ciascuno di essi parent_idvoglio restituire un solo messaggio. cioè, se più messaggi hanno lo stesso parent_id, posted_atviene restituito solo l'ultimo ( ). La parent_idpuò essere nullo, i messaggi con nulla parent_iddovrebbe tutto il ritorno.

La query che ho usato è:

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;

Ecco il http://sqlfiddle.com/#!15/588e5/1/0 , nel SQL Fiddle, ho definito lo schema, la query esatta e il risultato atteso.

Ma le prestazioni della query sono lente quando la tabella dei messaggi diventa grande. Ho provato ad aggiungere più indici di ordinamento, ma non sembra utilizzare l'indice. Ecco la spiegazione: http://explain.depesz.com/s/Sv2

Come posso creare un indice corretto?


A prima vista, ORDER BYla sottoquery è totalmente inutile. Inoltre, il piano collegato non può essere il risultato della query pubblicata metadata, ad esempio non viene menzionato .
dezso,

La tua descrizione non copre il ruolo di feed_ide posted_ate non hai menzionato metadataaffatto, che sembra essere un tipo JSON? Ripara la tua domanda per renderla coerente. Seleziona> 500k righe nel CTE ... Quante righe ci sono nella tabella? Quale percentuale di righe selezioni in genere nel CTE? Qual è la percentuale di righe parent_id IS NULL? Considera le informazioni nel tag [postgresql-performance] per le domande sulle prestazioni.
Erwin Brandstetter,

Anche importante: quante righe per ciascuna parent_id? (min / avg / max)
Erwin Brandstetter,

scusa, stavo cercando di chiarire la questione riducendo alcune delle colonne, share_count era in realtà in hstore metadata. Attualmente la tabella dei messaggi contiene 10 milioni di dati, ma sta aumentando rapidamente. Penso di separare in tabelle di partizione per ogni feed_id. Dal momento che sto recuperando solo per ID feed. la percentuale di parent_id null vs not null è di circa il 60% / 40%. un tipico recupero è circa l'1-2% della tabella. (circa 100 KB di messaggi) Le prestazioni per 100 KB sono di circa 1 secondo, ma una volta arriva a 500 KB + utilizza l'indice bitmap e normalmente impiega 10 secondi.
Zhaohan Weng,

Risposte:


9

domanda

Questa query dovrebbe essere sostanzialmente più veloce in ogni caso:

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;
  • Il CTE non fa nulla qui che una semplice sottoquery non possa fornire anche. E un CTE introduce una barriera di ottimizzazione poiché viene eseguito separatamente e il suo risultato viene materializzato.

  • Hai un livello di query secondario superiore a quello di cui hai effettivamente bisogno.

  • L'espressione (COALESCE(parent_id, message_id)non è compatibile con un indice semplice, sarebbe necessario un indice su quell'espressione. Ma potrebbe non essere molto utile, a seconda della distribuzione dei dati. Segui i miei link qui sotto per informazioni dettagliate.

  • Dividere il caso semplice parent_id IS NULLin un caso separato SELECTpuò o meno fornire l'ottimale. Soprattutto, se questo è un caso raro, nel qual caso una query combinata con un indice attivo (COALESCE(parent_id, message_id)potrebbe funzionare meglio. Altre considerazioni si applicano ...

Indici

Soprattutto quando supportato da questi indici:

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;

I due indici parziali coprono l'intera tabella insieme e hanno circa la stessa dimensione insieme di un singolo indice totale.

Le ultime due colonne parent_id, message_idhanno senso solo se si ottengono scansioni solo dell'indice. Altrimenti rimuoverli da entrambi gli indici.

SQL Fiddle.

A seconda dei dettagli mancanti, DISTINCT ONpotrebbe essere o meno la migliore tecnica di query per lo scopo. Leggi la spiegazione dettagliata qui:

E alternative forse più veloci qui:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.