Hvorfor?
årsagen er dette:
Hurtig forespørgsel:
-> 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)) ~~* (...)
Langsom forespørgsel:
-> 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 (...)
Udvidelse af søgemønsteret med et andet tegn får Postgres til at antage endnu færre hits. (Dette er typisk et rimeligt skøn.) Postgres har åbenbart ikke præcise nok statistikker (ingen, faktisk, fortsæt med at læse) til at forvente det samme antal hits, som du virkelig får.
Dette medfører et skift til en anden forespørgselsplan, som er endnu mindre optimal for den faktiske antal hits rows=1129
.
Løsning
Under den nuværende Postgres 9.5, da den ikke er blevet erklæret.
En måde at forbedre situationen på er at oprette et udtryksindeks på udtrykket i prædikatet. Dette får Postgres til at indsamle statistik for det faktiske udtryk, hvilket kan hjælpe forespørgslen selvom selve indekset ikke bruges til forespørgslen . Uden indekset er der ingen statistik for udtrykket overhovedet. Og hvis det gøres rigtigt, kan indekset bruges til forespørgslen, det er endnu meget bedre. Men der er flere problemer med dit nuværende udtryk:
unaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike unaccent('%vicen%' )
Overvej denne opdaterede forespørgsel baseret på nogle antagelser om dine ikke-oplyste tabeldefinitioner:
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' || '%');
Vigtige punkter
Hvorfor f_unaccent()
? Fordi unaccent()
kan ikke indekseres. Læs dette:
Jeg brugte funktionen skitseret der for at tillade følgende (anbefalet!) flerkolonne funktionelt trigram GIN indeks :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
Hvis du ikke er bekendt med trigramindekser, så læs dette først:
Og muligvis:
Sørg for at køre den seneste version af Postgres (i øjeblikket 9.5). Der er sket væsentlige forbedringer af GIN-indekser. Og du vil være interesseret i forbedringer i pg_trgm 1.2, der er planlagt til at blive frigivet med den kommende Postgres 9.6:
Forberedte erklæringer er en almindelig måde at udføre forespørgsler med parametre (især med tekst fra brugerinput). Postgres skal finde en plan, der fungerer bedst for en given parameter. Tilføj jokertegn som konstanter til søgeordet som dette:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
('vicenti'
ville blive erstattet med en parameter.) Så Postgres ved, at vi har at gøre med et mønster, der hverken er forankret til venstre eller højre - hvilket ville tillade forskellige strategier. Relateret svar med flere detaljer:
Eller måske omplanlægge forespørgslen for hver søgeterm (evt. ved hjælp af dynamisk SQL i en funktion). Men sørg for at planlægningstiden ikke tærer på nogen mulig præstationsgevinst.
WHERE
betingelse på kolonner i pess
modsiger . Postgres er tvunget til at konvertere det til en LEFT JOIN
INNER JOIN
. Hvad værre er sammenføjningen kommer sent i join-træet. Og da Postgres ikke kan genbestille dine joins (se nedenfor), kan det blive meget dyrt. Flyt tabellen til den første position i FROM
klausul for at eliminere rækker tidligt. Følger LEFT JOIN
s eliminerer ikke nogen rækker pr. definition. Men med så mange tabeller er det vigtigt at flytte joins, der kan multipliceres rækker til slutningen.
Du deltager i 13 borde, 12 af dem med LEFT JOIN
som efterlader 12!
mulige kombinationer - eller 11! * 2!
hvis vi tager den ene LEFT JOIN
i betragtning, at det virkelig er en INNER JOIN
. Det er også mange for Postgres at evaluere alle mulige permutationer for den bedste forespørgselsplan. Læs om join_collapse_limit
:
- Eksempelforespørgsel for at vise Cardinality-estimeringsfejl i PostgreSQL
- SQL INNER JOIN over flere tabeller svarende til WHERE-syntaks
Standardindstillingen for join_collapse_limit
er 8 , hvilket betyder, at Postgres ikke vil forsøge at omarrangere tabeller i din FROM
klausul og rækkefølgen af tabeller er relevant .
En måde at løse dette på ville være at opdele den præstationskritiske del i en CTE
like @joop kommenterede
. Indstil ikke join_collapse_limit
meget højere eller tider for forespørgselsplanlægning, der involverer mange sammenføjede tabeller, forringes.
Om din sammenkædede dato navngivet data
:
cast(cast(e.ano som varchar(4))||'-'||right('0'||cast(e.mes som varchar(2)),2)||' -'|| right('0'||cast(e.dia som varchar(2)),2) som varchar(10)) som data
Forudsat du bygger ud fra tre numeriske kolonner for år, måned og dag, som er defineret IKKE NULL
, brug dette i stedet:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
Om FM
skabelonmønstermodifikator:
Men egentlig bør du gemme datoen som datatype dato
til at begynde med.
Også forenklet:
format('%s (%s)', p.abrev, p.prenome) AS determinador
Vil ikke gøre forespørgslen hurtigere, men det er meget renere. Se format()
.
Først og fremmest, alle de sædvanlige råd til ydelsesoptimering gælder:
Hvis du får alt dette rigtigt, skulle du se meget hurtigere forespørgsler for alle mønstre.