Ich habe einen großen Tisch entities
mit ~ 15 Millionen Datensätzen. Ich möchte die Top 5 Reihen finden, die zu 'Hockey' passen name
.
Ich habe einen Volltextindex name
, der verwendet wird:gin_ix_entity_full_text_search_name
Abfrage:
SELECT "entities".*,
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE "entities"."place" = 'f'
AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text))
ORDER BY "rank0.48661998202865475" DESC LIMIT 5
Dauer 25.623 ms
Plan erklären 1 Limit (Kosten = 12666.89..12666.89 Zeilen = 5 Breite = 3116) 2 -> Sortieren (Kosten = 12666.89..12670.18 Zeilen = 6571 Breite = 3116) 3 Sortierschlüssel: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery)) 4 -> Bitmap-Heap-Scan für Entitäten (Kosten = 124.06..12645.06 Zeilen = 6571 Breite = 3116) 5 Überprüfen Sie erneut Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery) 6 Filter: (NICHT platzieren) 7 -> Bitmap-Index-Scan für gin_ix_entity_full_text_search_name (Kosten = 0,00..123,74 Zeilen = 6625 Breite = 0) 8 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
Ich verstehe nicht, warum es die Indexbedingung zweimal überprüft. (Abfrageplan Schritt 4 und 7). Liegt es an meiner booleschen Bedingung ( not place
)? Wenn ja, sollte ich es meinem Index hinzufügen, um eine sehr schnelle Abfrage zu erhalten? Oder macht es die Sortierbedingung langsam?
EXPLAIN ANALYZE
Ausgabe:
Limit (Kosten = 4447,28..4447,29 Zeilen = 5 Breite = 3116) (tatsächliche Zeit = 18509,274..18509,282 Zeilen = 5 Schleifen = 1) -> Sortieren (Kosten = 4447,28..4448,41 Zeilen = 2248 Breite = 3116) (tatsächliche Zeit = 18509,271..18509,273 Zeilen = 5 Schleifen = 1) Sortierschlüssel: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery)) Sortiermethode: Top-N-Heapsort Speicher: 19 KB -> Bitmap-Heap-Scan für Entitäten (Kosten = 43.31..4439.82 Zeilen = 2248 Breite = 3116) (tatsächliche Zeit = 119.003..18491.408 Zeilen = 2533 Schleifen = 1) Überprüfen Sie erneut Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Filter: (NICHT platzieren) -> Bitmap-Index-Scan für gin_ix_entity_full_text_search_name (Kosten = 0,00..43,20 Zeilen = 2266 Breite = 0) (tatsächliche Zeit = 74,093..74,093 Zeilen = 2593 Schleifen = 1) Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Gesamtlaufzeit: 18509.381 ms
Hier sind meine DB-Parameter. Es wird von Heroku auf Amazon-Diensten gehostet. Sie beschreiben es mit 1,7 GB RAM, 1 Prozessoreinheit und einer DB von maximal 1 TB.
Name | Aktuelle Einstellung ------------------------------ + ------------------- -------------------------------------------------- ------------------------------------ version | PostgreSQL 9.0.7 auf i486-pc-linux-gnu, kompiliert von GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32-Bit archive_command | test -f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push% p archive_mode | auf archive_timeout | 1 Minute checkpoint_completion_target | 0,7 checkpoint_segments | 40 client_min_messages | beachten cpu_index_tuple_cost | 0,001 cpu_operator_cost | 0,0005 cpu_tuple_cost | 0,003 effektive_cache_size | 1530000kB hot_standby | auf lc_collate | en_US.UTF-8 lc_ctype | en_US.UTF-8 listen_addresses | * * log_checkpoints | auf log_destination | Syslog log_line_prefix | % u [GELB] log_min_duration_statement | 50ms log_min_messages | beachten logging_collector | auf wartung_arbeit_mem | 64 MB max_connections | 500 max_prepared_transactions | 500 max_stack_depth | 2 MB max_standby_archive_delay | -1 max_standby_streaming_delay | -1 max_wal_senders | 10 port | random_page_cost | 2 server_encoding | UTF8 shared_buffers | 415 MB ssl | auf syslog_ident | resource29857_heroku_com Zeitzone | koordinierte Weltzeit wal_buffers | 8 MB wal_keep_segments | 127 wal_level | Hot-Standby work_mem | 100 MB (39 Zeilen)
BEARBEITEN
Sieht aus wie ORDER BY
ist der langsame Teil:
d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text))
LIMIT 5;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
-> Bitmap Heap Scan on entities (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
-> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
Total runtime: 103.680 ms
Vs. mit ORDER BY
:
d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text))
ORDER BY "rank0.48661998202865475" DESC
LIMIT 5;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
-> Sort (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
Sort Method: top-N heapsort Memory: 17kB
-> Bitmap Heap Scan on entities (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
-> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
Total runtime: 15013.805 ms
Ich verstehe immer noch nicht, warum das langsamer ist. Sieht so aus, als würde es die gleiche Anzahl von Zeilen aus Bitmap Heap Scan abrufen, aber es dauert so viel länger?
Antworten:
Dass das Sortieren der Zeilen etwas kostet , liegt auf der Hand. Aber warum so viel?
Ohne
ORDER BY rank0...
Postgres kann man einfachts_rank()
für nur 5 Zeilen.ts_rank()
für alle.ORDER BY name
nur, die Rechenkostento_tsquery('english', 'hockey'::text))
für die überflüssigen Zeilen zu ermitteln und festzustellen, wie viel für das Abrufen weiterer Zeilen und das Sortieren noch übrig ist.quelle
Ein Bitmap-Scan funktioniert folgendermaßen: Der Index wird gescannt, um die Positionen der Tupel zu finden, die den Indexbedingungen entsprechen. Die Bitmap kann eine logische Kombination mit den Ergebnissen anderer Bitmap-Scans durchlaufen, wobei eine boolesche Logik für die Bits verwendet wird. Dann wird auf die Seiten mit den Daten in der Reihenfolge der Seitenzahlen zugegriffen, um den Festplattenzugriff zu verringern und hoffentlich einige zufällige Lesevorgänge in sequentielle Lesevorgänge umzuwandeln.
Ein Bitmap-Index-Scan muss möglicherweise "verlustbehaftet" werden, um in den Speicher zu passen. Dadurch wird die Genauigkeit von der Tupelebene auf die Seitenebene reduziert. Wenn Sie work_mem erhöhen (zumindest für diese eine Abfrage), können Sie dies vermeiden. Es ist nicht möglich, den Bitmap-Heap-Scan in Zeile 4 oder den Filter in Zeile 6 zu überspringen, aber möglicherweise kann die erneute Überprüfung in Zeile 5 übersprungen werden.
quelle
Da die bearbeitete Frage so aussieht, als ob das Ziel darin besteht, einige der besten Übereinstimmungen auszuwählen, anstatt eine Liste aller Übereinstimmungen, veröffentliche ich eine separate Antwort darauf, da es sich um ein etwas anderes Problem handelt.
Vielleicht möchten Sie ein prüfen , KNN - GiST (für K Nearest Neighbor - Generalized Search Tree) Index, der direkt aus dem Index in der Reihenfolge der Ähnlichkeit ziehen kann - so dass Sie nicht zufällig alle jene Haufen Tupel zu lesen brauchen und sortieren sie unten, um die K besten Übereinstimmungen zu finden.
Bisher glaube ich nicht, dass irgendjemand die KNN-GIST-Unterstützung für Suchanfragen implementiert hat (obwohl mir versichert wurde, dass dies möglich ist, ist es nur eine Frage von jemandem, der sich die Zeit dafür nimmt), aber vielleicht die Trigram-Unterstützung (die ist erledigt) wird für Ihre Anwendung funktionieren. Der Hauptunterschied besteht darin, dass bei der Suche nach Trigrammen keine Wörterbücher für Stemming und Synonyme verwendet werden, wie dies bei der Suche der Fall ist. Sie finden nur genaue Wortübereinstimmungen.
Um Trigramme für Ihr Beispiel auszuprobieren, möchten Sie wahrscheinlich "Name" wie folgt indizieren:
Dann können Sie so suchen:
Beachten Sie die verwendeten Operatoren und den
ORDER BY
Alias der Spalte "Ähnlichkeit". Ich würde mich beim Ausprobieren nicht zu weit von diesem Muster entfernen. Der Index auf dem tsvector wird für diese Suche nicht verwendet.Abgesehen von Problemen mit Ihrer aktuellen Konfiguration (die Ihre gesamte VM aufgrund von Speicherüberlastung leicht in hoffnungsloses Paging versetzen könnten), wird Ihnen die Leistung wahrscheinlich sehr gut gefallen. Ob es das gewünschte Verhalten hat, weiß ich nicht.
quelle