PostgreSQL come variazioni delle prestazioni delle query


112

Ho riscontrato una variazione piuttosto ampia nei tempi di risposta per quanto riguarda le LIKEquery a una particolare tabella nel mio database. A volte ottengo risultati entro 200-400 ms (molto accettabile) ma altre volte potrebbero essere necessari fino a 30 secondi per restituire i risultati.

Capisco che le LIKEquery richiedono molte risorse, ma non capisco perché ci sarebbe una così grande differenza nei tempi di risposta. Ho costruito un indice btree sul owner1campo ma non credo che aiuti con le LIKEquery. Qualcuno ha qualche idea?

SQL di esempio:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

Ho anche provato:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

E:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

Con risultati simili.
Numero di righe della tabella: circa 95.000.

Risposte:


282

FTS non supporta LIKE

La risposta precedentemente accettata non era corretta. La ricerca full text con i suoi indici full text non è affatto per l' LIKEoperatore, ha i propri operatori e non funziona per stringhe arbitrarie. Funziona su parole basate su dizionari e stemming. Si fa il supporto prefisso di corrispondenza per le parole , ma non con l' LIKEoperatore:

Indici Trigram per LIKE

Installare il modulo aggiuntivo pg_trgmche prevede classi di operatori per gin GiST trigram indici per supportare tutte LIKEe ILIKEmodelli , non appena lasciato ancorati quelli:

Indice di esempio:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

O:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

Query di esempio:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

Trigrammi? E le stringhe più corte?

Le parole con meno di 3 lettere nei valori indicizzati funzionano ancora. Il manuale:

Si considera che ogni parola abbia due spazi prefissati e uno spazio come suffisso quando si determina l'insieme di trigrammi contenuti nella stringa.

E modelli di ricerca con meno di 3 lettere? Il manuale:

Per entrambe LIKEle ricerche con espressioni regolari, tieni presente che un modello senza trigrammi estraibili degenererà in una scansione completa dell'indice.

Ciò significa che le scansioni dell'indice / indice bitmap funzionano ancora (i piani di query per l'istruzione preparata non si interrompono), semplicemente non ti offriranno prestazioni migliori. In genere nessuna grande perdita, poiché le stringhe di 1 o 2 lettere sono difficilmente selettive (più di una piccola percentuale delle corrispondenze di tabella sottostanti) e il supporto dell'indice non migliorerebbe le prestazioni all'inizio, perché una scansione completa della tabella è più veloce.


text_pattern_ops per la corrispondenza del prefisso

Per i modelli ancorati a sinistra (nessun carattere jolly iniziale) si ottiene l'ottimale con una classe operatore adatta per un indice btree: text_pattern_opso varchar_pattern_ops. Entrambe le funzionalità integrate di Postgres standard, non sono necessari moduli aggiuntivi. Prestazioni simili, ma indice molto più piccolo.

Indice di esempio:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Query di esempio:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

Oppure , se si dovrebbe eseguire il database con la locale "C" (effettivamente nessuna locale), allora tutto viene ordinato comunque in base all'ordine dei byte e un semplice indice btree con la classe operatore predefinita fa il lavoro.

Maggiori dettagli, spiegazioni, esempi e collegamenti in queste risposte correlate su dba.SE:


Senza caratteri jolly iniziali su una tabella di 500.000 linee, l'indice gin con gin_trgm_ops sembra essere 10 volte più veloce di btree
nicolas

@nicolas: il confronto dipende da molte variabili. Lunghezza della chiave, distribuzione dei dati, lunghezza del modello, possibile scansione solo dell'indice ... E soprattutto: versione di Postgres. Gli indici GIN sono stati migliorati sostanzialmente nelle pagine 9.4 e 9.5. Una nuova versione di pg_trgm (che verrà rilasciata con pg 9.6) porterà ulteriori miglioramenti.
Erwin Brandstetter

1
Se ho capito bene i documenti, pg_trgmhai bisogno di una stringa di query di almeno 3 caratteri di lunghezza, ad esempio fo%non colpirebbe l'indice ma farebbe invece una scansione. Qualcosa da notare.
Tuukka Mustonen

1
@TuukkaMustonen: Buon punto. Bene, le scansioni dell'indice (bitmap) funzionano ancora , semplicemente non ti offriranno prestazioni migliori. Ho aggiunto qualche chiarimento sopra.
Erwin Brandstetter

7

Forse quelli veloci sono modelli ancorati con distinzione tra maiuscole e minuscole che possono usare indici. cioè non ci sono caratteri jolly all'inizio della stringa di corrispondenza, quindi l'esecutore può utilizzare una scansione dell'intervallo di indice. ( il commento pertinente nella documentazione è qui ) Anche Lower e ilike perderanno la capacità di utilizzare l'indice a meno che non si crei specificamente un indice a tale scopo (vedere indici funzionali ).

Se desideri cercare una stringa al centro del campo, dovresti esaminare il testo completo o gli indici trigramma . Il primo è nel core di Postgres, l'altro è disponibile nei moduli contrib.


Non avevo pensato di creare un indice sul valore minuscolo del campo. In questo modo posso convertire il testo della query in minuscolo sul back-end prima di eseguire la query.
Jason

4

È possibile installare Wildspeed , un diverso tipo di indice in PostgreSQL. Wildspeed funziona con i caratteri jolly% word%, nessun problema. Lo svantaggio è la dimensione dell'indice, che può essere grande, molto grande.


3

Eseguire la query sotto indicata per migliorare le prestazioni delle query LIKE in postgresql. crea un indice come questo per tabelle più grandi:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

Funziona solo se il pattern non inizia con un carattere jolly: in questo caso le prime due query di esempio iniziano entrambe con un carattere jolly.
cbz


1

Recentemente ho avuto un problema simile con una tabella contenente 200000 record e devo eseguire ripetute query LIKE. Nel mio caso, la stringa da cercare è stata corretta. Altri campi variavano. Perché questo, sono stato in grado di riscrivere:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

come

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

Sono stato felicissimo quando le query sono tornate rapidamente e ho verificato che l'indice viene utilizzato con EXPLAIN ANALYZE:

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

Le tue query like probabilmente non possono utilizzare gli indici che hai creato perché:

1) i tuoi criteri LIKE iniziano con un carattere jolly.

2) hai utilizzato una funzione con i tuoi criteri LIKE.


0

Ogni volta che usi una clausola su una colonna con funzioni come LIKE, ILIKE, upper, lower, ecc. Allora postgres non prenderà in considerazione il tuo indice normale. Farà una scansione completa della tabella che passa attraverso ogni riga e quindi sarà lenta.

Il modo corretto sarebbe creare un nuovo indice in base alla tua query. Ad esempio, se voglio abbinare una colonna senza distinzione tra maiuscole e minuscole e la mia colonna è un varchar. Allora puoi farlo in questo modo.

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

Allo stesso modo, se la tua colonna è un testo, fai qualcosa di simile

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

Allo stesso modo puoi cambiare la funzione superiore con qualsiasi altra funzione che desideri.

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.