Perché?
Il motivo è questo:
Domanda veloce:
-> Hash Left Join (cost=1378.60..2467.48 rows=15 width=79) (actual time=41.759..85.037 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* (...)
Domanda lenta:
-> Hash Left Join (cost=1378.60..2467.48 rows=1 width=79) (actual time=35.084..80.209 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* unacc (...)
L'estensione del modello di ricerca di un altro personaggio fa sì che Postgres assuma ancora meno risultati. (In genere, questa è una stima ragionevole.) Postgres ovviamente non ha statistiche sufficientemente precise (nessuna, in realtà, continua a leggere) per aspettarsi lo stesso numero di risultati che ottieni davvero.
Ciò provoca il passaggio a un piano di query diverso, che è ancora meno ottimale per il effettivo numero di hit rows=1129
.
Soluzione
Supponendo l'attuale Postgres 9.5 poiché non è stato dichiarato.
Un modo per migliorare la situazione è creare un indice di espressione sull'espressione nel predicato. Ciò fa sì che Postgres raccolga statistiche per l'espressione effettiva, che può aiutare la query anche se l'indice stesso non viene utilizzato per la query . Senza l'indice, non ci sono nessuna statistica per l'espressione a tutti. E se fatto correttamente, l'indice può essere utilizzato per la query, è anche molto meglio. Ma ci sono molti problemi con la tua espressione attuale:
unaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike unaccent('%vicen%')
Considera questa query aggiornata, basata su alcuni presupposti sulle definizioni delle tabelle non divulgate:
SELECT e.id
, (SELECT count(*) FROM imgitem
WHERE tabid = e.id AND tab = 'esp') AS imgs -- count(*) is faster
, e.ano, e.mes, e.dia
, e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
, pl.pltag, e.inpa, e.det, d.ano anodet
, format('%s (%s)', p.abrev, p.prenome) AS determinador
, d.tax
, coalesce(v.val,v.valf) || ' ' || vu.unit AS altura
, coalesce(v1.val,v1.valf) || ' ' || vu1.unit AS dap
, d.fam, tf.nome família, d.gen, tg.nome AS gênero, d.sp
, ts.nome AS espécie, d.inf, e.loc, l.nome localidade, e.lat, e.lon
FROM pess p -- reorder!
JOIN det d ON d.detby = p.id -- INNER JOIN !
LEFT JOIN tax tf ON tf.oldfam = d.fam
LEFT JOIN tax tg ON tg.oldgen = d.gen
LEFT JOIN tax ts ON ts.oldsp = d.sp
LEFT JOIN tax ti ON ti.oldinf = d.inf -- unused, see @joop's comment
LEFT JOIN esp e ON e.det = d.id
LEFT JOIN loc l ON l.id = e.loc
LEFT JOIN var v ON v.esp = e.id AND v.key = 265
LEFT JOIN varunit vu ON vu.id = v.unit
LEFT JOIN var v1 ON v1.esp = e.id AND v1.key = 264
LEFT JOIN varunit vu1 ON vu1.id = v1.unit
LEFT JOIN pl ON pl.id = e.pl
WHERE f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%') OR
f_unaccent(p.prenome) ILIKE f_unaccent('%' || 'vicenti' || '%');
Punti principali
Perché f_unaccent()
? Perché unaccent()
non può essere indicizzato. Leggi questo:
Ho usato la funzione qui delineata per consentire il seguente (consigliato!) trigramma funzionale multicolonna GIN indice :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
Se non hai familiarità con gli indici trigram, leggi prima questo:
E possibilmente:
Assicurati di eseguire l'ultima versione di Postgres (attualmente 9.5). Sono stati apportati miglioramenti sostanziali agli indici GIN. E sarai interessato ai miglioramenti in pg_trgm 1.2, programmato per essere rilasciato con il prossimo Postgres 9.6:
Dichiarazioni preparate sono un modo comune per eseguire query con parametri (soprattutto con il testo dell'input dell'utente). Postgres deve trovare un piano che funzioni meglio per ogni dato parametro. Aggiungi caratteri jolly come costanti al termine di ricerca in questo modo:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
('vicenti'
verrebbe sostituito con un parametro.) Quindi Postgres sa che abbiamo a che fare con un modello che non è né ancorato a sinistra né a destra, il che consentirebbe strategie diverse. Risposta correlata con maggiori dettagli:
O forse riorganizzare la query per ogni termine di ricerca (possibilmente utilizzando SQL dinamico in una funzione). Ma assicurati che il tempo di pianificazione non stia consumando alcun possibile guadagno in termini di prestazioni.
Il WHERE
condizione sulle colonne in pess
contraddice lo . Postgres è obbligata a convertirlo in un LEFT JOIN
INNER JOIN
. Quel che è peggio, il join arriva in ritardo nell'albero dei join. E poiché Postgres non può riordinare i tuoi join (vedi sotto), può diventare molto costoso. Sposta la tabella al primo posizione nel FROM
clausola per eliminare le righe in anticipo. Seguendo LEFT JOIN
s non elimina alcuna riga per definizione. Ma con così tante tabelle è importante spostare i join che potrebbero moltiplicarsi righe fino alla fine.
Ti stai unendo a 13 tavoli, 12 dei quali con LEFT JOIN
che lascia 12!
combinazioni possibili - o 11! * 2!
se prendiamo quello LEFT JOIN
in considerazione che è davvero un INNER JOIN
. È anche molti per Postgres per valutare tutte le possibili permutazioni per il miglior piano di query. Leggi su join_collapse_limit
:
- Esempio di query per mostrare l'errore di stima della cardinalità in PostgreSQL
- SQL INNER JOIN su più tabelle uguale alla sintassi WHERE
L'impostazione predefinita per join_collapse_limit
è 8 , il che significa che Postgres non proverà a riordinare le tabelle nel tuo FROM
clausola e l'ordine delle tabelle è rilevante .
Un modo per aggirare questo problema sarebbe dividere la parte critica per le prestazioni in un CTE
come @joop ha commentato
. Non impostare join_collapse_limit
molto più alto o i tempi per la pianificazione delle query che coinvolgono molte tabelle unite si deterioreranno.
Informazioni sulla tua data concatenata denominato data
:
cast(cast(e.ano as varchar(4))||'-'||right('0'||cast(e.mes as varchar(2)),2)||'-'|| right('0'||cast(e.dia as varchar(2)),2) as varchar(10)) as data
Supponendo costruisci da tre colonne numeriche per anno, mese e giorno, che sono definite NOT NULL
, usa invece questo:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
Informazioni su FM
modificatore modello modello:
Ma in realtà, dovresti memorizzare la data come tipo di dati date
per cominciare.
Anche semplificato:
format('%s (%s)', p.abrev, p.prenome) AS determinador
Non renderà la query più veloce, ma è molto più pulita. Vedi format()
.
Per prima cosa, tutti i soliti consigli per l'ottimizzazione delle prestazioni si applica:
Se fai tutto bene, dovresti vedere query molto più veloci per tutti modelli.