Variationen der PostgreSQL LIKE-Abfrageleistung


113

Ich habe eine große Variation der Antwortzeiten in Bezug auf LIKEAbfragen zu einer bestimmten Tabelle in meiner Datenbank festgestellt . Manchmal erhalte ich Ergebnisse innerhalb von 200-400 ms (sehr akzeptabel), aber manchmal kann es bis zu 30 Sekunden dauern, bis die Ergebnisse zurückgegeben werden.

Ich verstehe, dass LIKEAbfragen sehr ressourcenintensiv sind, aber ich verstehe einfach nicht, warum es einen so großen Unterschied in den Antwortzeiten geben würde. Ich habe einen Btree-Index für das owner1Feld erstellt, aber ich glaube nicht, dass er bei LIKEAbfragen hilft . Hat jemand irgendwelche Ideen?

Beispiel-SQL:

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

Ich habe auch versucht:

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

Und:

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

Mit ähnlichen Ergebnissen.
Anzahl der Tabellenzeilen: ca. 95.000.

Antworten:


280

FTS unterstützt nicht LIKE

Die zuvor akzeptierte Antwort war falsch. Die Volltextsuche mit ihren Volltextindizes ist überhaupt nicht für den LIKEOperator, sie hat ihre eigenen Operatoren und funktioniert nicht für beliebige Zeichenfolgen. Es arbeitet mit Wörtern, die auf Wörterbüchern und Stemming basieren. Es tut Unterstützung für Worte Abgleich von Präfixen , aber nicht mit dem LIKEBetreiber:

Trigrammindizes für LIKE

Installieren Sie das zusätzliche Modul pg_trgm, das Operatorklassen für GIN- und GiST-Trigrammindizes bereitstellt , um alle LIKEund ILIKEMuster zu unterstützen , nicht nur links verankerte:

Beispielindex:

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

Oder:

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

Beispielabfrage:

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

Trigramme? Was ist mit kürzeren Saiten?

Wörter mit weniger als 3 Buchstaben in indizierten Werten funktionieren weiterhin. Das Handbuch:

Bei jedem Wort werden zwei Leerzeichen und ein Leerzeichen vorangestellt, wenn die in der Zeichenfolge enthaltene Menge von Trigrammen bestimmt wird.

Und Suchmuster mit weniger als 3 Buchstaben? Das Handbuch:

Für beide LIKEund regulären Ausdrücken sucht, bedenken Sie, dass ein Muster ohne ausziehbare trigrams zu einem Full-Index - Scan degeneriert wird.

Das bedeutet, dass Index- / Bitmap-Index-Scans immer noch funktionieren (Abfragepläne für vorbereitete Anweisungen werden nicht unterbrochen). Sie erhalten einfach keine bessere Leistung. Normalerweise kein großer Verlust, da Zeichenfolgen mit 1 oder 2 Buchstaben kaum selektiv sind (mehr als ein paar Prozent der zugrunde liegenden Tabellenübereinstimmungen) und die Indexunterstützung die Leistung zunächst nicht verbessern würde, da ein vollständiger Tabellenscan schneller ist.


text_pattern_ops für Präfixabgleich

Für nur links verankerte Muster (kein führender Platzhalter) erhalten Sie das Optimum mit einer geeigneten Operatorklasse für einen btree-Index: text_pattern_opsoder varchar_pattern_ops. Beide integrierten Funktionen von Standard-Postgres, kein zusätzliches Modul erforderlich. Ähnliche Leistung, aber viel kleinerer Index.

Beispielindex:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Beispielabfrage:

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

Oder sollten , wenn Sie Ihre Datenbank mit dem laufen ‚C‘ locale (effektiv keine locale), dann ist sowieso alles nach Byte - Reihenfolge sortiert und ein einfaches btree Index mit Standardoperatorklasse macht den Job.

Weitere Details, Erklärungen, Beispiele und Links in diesen verwandten Antworten auf dba.SE:


Ohne führenden Platzhalter in einer Tabelle mit 500.000 Zeilen scheint der Gin-Index mit gin_trgm_ops zehnmal schneller zu sein als btree
nicolas

@nicolas: Der Vergleich hängt von vielen Variablen ab. Schlüssellänge, Datenverteilung, Musterlänge, möglicher Nur-Index-Scan ... Und vor allem: Postgres-Version. Die GIN-Indizes wurden in den Seiten 9.4 und 9.5 erheblich verbessert. Die neue Version von pg_trgm (wird mit Seite 9.6 veröffentlicht) wird weitere Verbesserungen bringen.
Erwin Brandstetter

1
Wenn ich die Dokumente richtig verstanden habe, pg_trgmbenötigen Sie eine Abfragezeichenfolge mit mindestens 3 Zeichen Länge, die beispielsweise fo%nicht den Index trifft, sondern stattdessen einen Scan durchführt. Etwas zu beachten.
Tuukka Mustonen

1
@ TuukkaMustonen: Guter Punkt. Nun, (Bitmap-) Index-Scans funktionieren immer noch , sie bringen Ihnen einfach keine bessere Leistung. Ich habe oben einige Klarstellungen hinzugefügt.
Erwin Brandstetter

7

Möglicherweise sind die schnellen Muster verankerte Muster, bei denen zwischen Groß- und Kleinschreibung unterschieden wird und Indizes verwendet werden können. Das heißt, am Anfang der Übereinstimmungszeichenfolge befindet sich kein Platzhalter, sodass der Executor einen Indexbereichsscan verwenden kann. ( Der entsprechende Kommentar in den Dokumenten ist hier ) Lower und ilike verlieren auch Ihre Fähigkeit, den Index zu verwenden, es sei denn, Sie erstellen speziell einen Index für diesen Zweck (siehe Funktionsindizes ).

Wenn Sie in der Mitte des Feldes nach Zeichenfolgen suchen möchten, sollten Sie sich Volltext- oder Trigrammindizes ansehen . Der erste befindet sich im Postgres-Kern, der andere ist in den Contrib-Modulen verfügbar.


Ich hatte nicht daran gedacht, einen Index für den Kleinbuchstaben des Feldes zu erstellen. Auf diese Weise kann ich den Abfragetext im Backend vor dem Abfragen in Kleinbuchstaben konvertieren.
Jason

4

Sie können Wildspeed installieren , einen anderen Indextyp in PostgreSQL. Wildspeed funktioniert mit% word% Wildcards, kein Problem. Der Nachteil ist die Größe des Index, dies kann groß sein, sehr groß.


3

Führen Sie die unten genannte Abfrage aus, um die Leistung der LIKE-Abfrage in postgresql zu verbessern. Erstellen Sie einen solchen Index für größere Tabellen:

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

Dies funktioniert nur, wenn das Muster nicht mit einem Platzhalter beginnt. In diesem Fall beginnen die ersten beiden Beispielabfragen beide mit einem Platzhalter.
cbz


1

Ich hatte kürzlich ein ähnliches Problem mit einer Tabelle mit 200000 Datensätzen und muss wiederholte LIKE-Abfragen durchführen. In meinem Fall wurde die gesuchte Zeichenfolge behoben. Andere Bereiche waren unterschiedlich. Dadurch konnte ich umschreiben:

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

wie

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;

Ich war begeistert, als die Anfragen schnell zurückkamen und bestätigten, dass der Index verwendet wird mit 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

Ihre ähnlichen Abfragen können die von Ihnen erstellten Indizes wahrscheinlich nicht verwenden, weil:

1) Ihre LIKE-Kriterien beginnen mit einem Platzhalter.

2) Sie haben eine Funktion mit Ihren LIKE-Kriterien verwendet.


0

Wann immer Sie eine Klausel für eine Spalte mit Funktionen wie LIKE, ILIKE, Upper, Lower usw. verwenden, berücksichtigen Postgres Ihren normalen Index nicht. Es wird ein vollständiger Scan der Tabelle durchgeführt, die durch jede Zeile geht, und daher wird es langsam sein.

Der richtige Weg wäre, einen neuen Index entsprechend Ihrer Abfrage zu erstellen. Zum Beispiel, wenn ich eine Spalte ohne Groß- und Kleinschreibung abgleichen möchte und meine Spalte ein Varchar ist. Dann können Sie es so machen.

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

Wenn Ihre Spalte ein Text ist, tun Sie dies auch

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

Ebenso können Sie die obere Funktion in eine beliebige andere Funktion ändern.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.