Ich habe einen Postgres-Tisch mit mehr als 20 Millionen Tupeln:
first_name | last_name | email
-------------------------------------------
bat | man | batman@wayne.com
arya | vidal | foo@email.com
max | joe | bar@email.com
So filtern Sie die von mir verwendeten Datensätze:
SELECT *
FROM people
WHERE (first_name || '' || last_name) ILIKE '%bat%man%' OR
first_name ILIKE '%bat%man%' OR
last_name ILIKE '%bat%man%' OR
email ILIKE '%bat%man%'
LIMIT 25 OFFSET 0
Selbst bei Indizes dauert die Suche fast eine Minute , um Ergebnisse zurückzugeben.
Es gibt Indizes für (first_name || '' || last_name)
, first_name
, last_name
und email
.
Was kann ich tun, um die Leistung dieser Abfrage zu verbessern?
ilike '%something'
(mit führendem Platzhalter). Sie müssen andere Indizes hinzufügen.CREATE TABLE
Skript oder was Sie\d people
in psql erhalten.*_pattern_ops
Operatorklassen sind nützlich für links verankerte Muster, aber für den demonstrierten Anwendungsfall machtlos.Antworten:
Verwenden Sie für Ihre Art der Mustererkennung am besten einen Trigrammindex. Lesen Sie dies zuerst:
Ich gehe davon aus, dass Ihr Ausdruck einen Tippfehler enthält
(first_name || '' || last_name)
, der bei einer leeren Zeichenfolge keinen Sinn ergibt, und Sie möchten wirklich(first_name || ' ' || last_name)
- mit einem Leerzeichen.Unter der Annahme, dass jede Spalte NULL sein kann, benötigen Sie eine NULL-sichere Verkettung. Die einfache Lösung lautet
concat_ws()
:Diese Funktion ist jedoch nicht
IMMUTABLE
(Erklärung in der verknüpften Antwort), sodass Sie sie nicht direkt in einem Indexausdruck verwenden können. Sie könnten einenIMMUTABLE
Funktions-Wrapper verwenden:Der Wrapper kann sein,
IMMUTABLE
weil er nurtext
Parameter akzeptiert.In beiden Fällen ist dies ausführlicher, hat jedoch weniger internen Aufwand und ist erheblich schneller:
Oder mit fest codiertem Leerzeichen:
Basierend auf dieser Funktion schlage ich vor:
Ich habe
email
als zweite Indexspalte für mehrere Überlegungen hinzugefügt .Das Erstellen des Index dauert eine Weile für 20 Millionen Zeilen, am besten nicht während des Topladens oder möglicherweise bei Verwendung
CREATE INDEX CONCURRENTLY ...
. Ein GIN-Index ist erheblich größer als ein einfacher Btree-Index und in der Wartung auch teurer. Stellen Sie sicher, dass Sie die neueste Version von Postgres ausführen. In den letzten Versionen wurden die GIN-Indizes erheblich verbessert.Dann sollte Ihre leicht angepasste und vereinfachte Abfrage schnell und korrekt sein :
Sie benötigen nur den einen Index für diese Abfrage.
Grundlagen für den Mustervergleich:
quelle
concat_ws
, Sie wissen , warum es nichtimmutable
? Ich würde vorschlagen, dies vor dem Einpacken zu überprüfen, da Sie sonst möglicherweise Indexinhalte erhalten, die nicht mit den aktuellen Ausdrucksergebnissen für dieselbe Eingabe übereinstimmen, was sich wie eine Indexbeschädigung verhält.concat_ws()
nicht bin,IMMUTABLE
und der oben genannten Antwort eine Erklärung hinzugefügt . Die Erklärung ist ziemlich einfach und mein Wrapper ist sicher, weil er nurtext
Parameter akzeptiert.