PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Ottimizzazione di più join

Ci sono sempre 2 cose da considerare quando si ottimizzano le query:

  • Quali indici possono essere utilizzati (potrebbe essere necessario creare indici)
  • Come viene scritta la query (potrebbe essere necessario modificare la query per consentire a Query Optimizer di trovare gli indici appropriati e per non rileggere i dati in modo ridondante)

Alcune osservazioni:

  • Stai eseguendo manipolazioni di date prima di unirti alle tue date. Come regola generale, ciò impedirà a un ottimizzatore di query di utilizzare un indice anche se esiste. Dovresti provare a scrivere le tue espressioni in modo tale che le colonne indicizzate esistano inalterate su un lato dell'espressione.

  • Le tue sottoquery filtrano in base allo stesso intervallo di date di generate_series . Questa è una duplicazione e limita la capacità dell'ottimizzatore di scegliere l'ottimizzazione più efficiente. Sospetto che potrebbe essere stato scritto per migliorare le prestazioni perché l'ottimizzatore non è stato in grado di utilizzare un indice nella colonna della data (body_time )?

  • NOTA :Ci piacerebbe davvero molto usare un indice su Body.body_time

  • ORDER BY all'interno delle sottoquery è nella migliore delle ipotesi ridondante. Nel peggiore dei casi potrebbe forzare l'ottimizzatore di query a ordinare il set di risultati prima di unirsi; e ciò non è necessariamente positivo per il piano di query. Piuttosto, applica l'ordine solo alla fine per la visualizzazione finale.

  • Utilizzo di LEFT JOIN nelle tue sottoquery è inappropriato. Supponendo che tu stia utilizzando le convenzioni ANSI per NULL comportamento (e dovresti esserlo), qualsiasi esterno si unisce a envelope restituirebbe envelope_command=NULL , e questi sarebbero di conseguenza esclusi dalla condizione envelope_command=? .

  • Sottoquery o e i sono quasi identici tranne che per envelope_command valore. Ciò costringe l'ottimizzatore a scansionare due volte le stesse tabelle sottostanti. Puoi utilizzare una tabella pivot tecnica per unire i dati una volta e dividere i valori in 2 colonne.

Prova quanto segue che utilizza la tecnica pivot:

SELECT  p.period,
        /*The pivot technique in action...*/
        SUM(
        CASE WHEN envelope_command = 1 THEN body_size
        ELSE 0
        END) AS Outbound,
        SUM(
        CASE WHEN envelope_command = 2 THEN body_size
        ELSE 0
        END) AS Inbound
FROM    (
        SELECT  date '2009-10-01' + s.day AS period
        FROM    generate_series(0, date '2009-10-31' - date '2009-10-01') AS s(day)
        ) AS p 
        /*The left JOIN is justified to ensure ALL generated dates are returned
          Also: it joins to a subquery, else the JOIN to envelope _could_ exclude some generated dates*/
        LEFT OUTER JOIN (
        SELECT  b.body_size,
                b.body_time,
                e.envelope_command
        FROM    body AS b 
                INNER JOIN envelope e 
                  ON e.message_id = b.message_id 
        WHERE   envelope_command IN (1, 2)
        ) d
          /*The expressions below allow the optimser to use an index on body_time if 
            the statistics indicate it would be beneficial*/
          ON d.body_time >= p.period
         AND d.body_time < p.period + INTERVAL '1 DAY'
GROUP BY p.Period
ORDER BY p.Period

MODIFICA :Aggiunto filtro suggerito da Tom H.