sql >> Database >  >> RDS >> PostgreSQL

Waarom vertraagt ​​een kleine verandering in de zoekterm de zoekopdracht zo veel?

Waarom?

De reden is dit:

Snelle vraag:

->  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)) ~~* (...)

Langzame zoekopdracht:

->  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 (...)

Door het zoekpatroon uit te breiden met een ander karakter neemt Postgres nog minder treffers aan. (Normaal gesproken is dit een redelijke schatting.) Postgres heeft duidelijk niet nauwkeurig genoeg statistieken (geen enkele, blijf lezen) om hetzelfde aantal hits te verwachten als u werkelijk krijgt.

Dit veroorzaakt een overstap naar een ander zoekplan, dat nog minder optimaal is voor de werkelijke aantal hits rows=1129 .

Oplossing

Ervan uitgaande dat de huidige Postgres 9.5 niet is gedeclareerd.

Een manier om de situatie te verbeteren is het maken van een expressie-index op de uitdrukking in het predikaat. Dit zorgt ervoor dat Postgres statistieken verzamelt voor de eigenlijke expressie, wat de zoekopdracht kan helpen zelfs als de index zelf niet wordt gebruikt voor de zoekopdracht . Zonder de index zijn er geen statistieken helemaal voor de uitdrukking. En als het goed wordt gedaan, kan de index worden gebruikt voor de query, dat is nog veel beter. Maar er zijn meerdere problemen met je huidige uitdrukking:

unaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike unaccent('%vicen%')

Overweeg deze bijgewerkte zoekopdracht, gebaseerd op enkele aannames over uw niet-openbaar gemaakte tabeldefinities:

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' || '%');

Belangrijkste punten

Waarom f_unaccent() ? Omdat unaccent() kan niet worden geïndexeerd. Lees dit:

Ik heb de daar beschreven functie gebruikt om het volgende (aanbevolen!) functionele trigram met meerdere kolommen GIN index toe te staan :

CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);

Als u niet bekend bent met trigram-indexen, lees dan eerst dit:

En mogelijk:

Zorg ervoor dat u de nieuwste versie van Postgres gebruikt (momenteel 9.5). Er zijn aanzienlijke verbeteringen aangebracht in de GIN-indexen. En u zult geïnteresseerd zijn in verbeteringen in pg_trgm 1.2, gepland voor release met de komende Postgres 9.6:

Voorbereide verklaringen zijn een gebruikelijke manier om query's met parameters uit te voeren (vooral met tekst van gebruikersinvoer). Postgres moet een plan vinden dat het beste werkt voor een bepaalde parameter. Voeg jokertekens toe als constanten naar de naar de zoekterm als volgt:

f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')

('vicenti' zou worden vervangen door een parameter.) Dus Postgres weet dat we te maken hebben met een patroon dat noch links noch rechts verankerd is - wat verschillende strategieën mogelijk zou maken. Gerelateerd antwoord met meer details:

Of misschien de query opnieuw plannen voor elke zoekterm (eventueel met behulp van dynamische SQL in een functie). Maar zorg ervoor dat de planningstijd niet ten koste gaat van mogelijke prestatiewinst.

De WHERE voorwaarde op kolommen in pess is in tegenspraak met de LEFT JOIN . Postgres wordt gedwongen om dat om te zetten in een INNER JOIN . Wat erger is, de join komt laat in de join-boom. En aangezien Postgres uw joins niet opnieuw kan ordenen (zie hieronder), kan dat erg duur worden. Verplaats de tabel naar de eerste positie in de FROM clausule om rijen vroegtijdig te elimineren. Volgend LEFT JOIN s elimineren per definitie geen rijen. Maar met zoveel tafels is het belangrijk om joins te verplaatsen die kunnen vermenigvuldigen rijen naar het einde.

Je sluit je aan bij 13 tafels, waarvan 12 met LEFT JOIN wat resulteert in 12! mogelijke combinaties - of 11! * 2! als we de ene nemen LEFT JOIN houd er rekening mee dat is echt een INNER JOIN . Dat is te veel voor Postgres om alle mogelijke permutaties te evalueren voor het beste queryplan. Lees over join_collapse_limit :

De standaardinstelling voor join_collapse_limit is 8 , wat betekent dat Postgres niet zal proberen de tabellen opnieuw te ordenen in uw FROM clausule en de volgorde van tabellen is relevant .

Een manier om dit te omzeilen zou zijn om het prestatiekritische deel te splitsen in een CTE zoals @joop heeft gereageerd . Stel join_collapse_limit niet in veel hoger of de tijden voor het plannen van query's waarbij veel samengevoegde tabellen betrokken zijn, zullen verslechteren.

Over je aaneengeschakelde datum genaamd 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

Aangenomen je bouwt uit drie numerieke kolommen voor jaar, maand en dag, die zijn gedefinieerd NOT NULL , gebruik in plaats daarvan dit:

e.ano::text || to_char(e.mes2, 'FM"-"00')
            || to_char(e.dia,  'FM"-"00') AS data

Over de FM sjabloon patroon modifier:

Maar eigenlijk moet u de datum opslaan als gegevenstype date om mee te beginnen.

Ook vereenvoudigd:

format('%s (%s)', p.abrev, p.prenome) AS determinador

Zal de query niet sneller maken, maar het is veel schoner. Zie format() .

Allereerst, al het gebruikelijke advies voor prestatie-optimalisatie van toepassing:

Als je dit allemaal goed doet, zou je veel snellere zoekopdrachten moeten zien voor alle patronen.



  1. Hoe gebruik ik de Embedded PostgreSQL Server Java-component als een aparte service?

  2. Is een RID Lookup sneller dan een Key Lookup?

  3. inconsistente datatypes:bij het retourneren van tabel van cursor in een pakketfunctie - ORACLE

  4. Fout java.lang.ClassNotFoundException:com.mysql.jdbc.Driver tijdens het uitvoeren van JAR via de opdrachtregel