Die Ausführung von Volltextabfragen für diese Datenbank (Speichern von RT- Tickets ( Request Tracker )) scheint sehr lange zu dauern. Die Anhangstabelle (die die Volltextdaten enthält) umfasst ca. 15 GB.
Das Datenbankschema lautet wie folgt: Es sind ungefähr 2 Millionen Zeilen:
rt4 = # \ d + Anhänge Tabelle "public.attachments" Spalte | Geben Sie | ein Modifikatoren | Lagerung | Beschreibung ----------------- + ----------------------------- + - -------------------------------------------------- ------ + ---------- + ------------- id | Ganzzahl | nicht null default nextval ('attachments_id_seq' :: regclass) | schlicht | Transaktions-ID | Ganzzahl | nicht null | schlicht | Eltern | Ganzzahl | nicht null default 0 | schlicht | messageid | Zeichen variierend (160) | | erweitert | Betreff | Zeichen variierend (255) | | erweitert | Dateiname | Zeichen variierend (255) | | erweitert | Inhaltstyp | Zeichen variierend (80) | | erweitert | contentencoding | Zeichen variierend (80) | | erweitert | Inhalt | Text | | erweitert | Überschriften | Text | | erweitert | Schöpfer | Ganzzahl | nicht null default 0 | schlicht | erstellt | Zeitstempel ohne Zeitzone | | schlicht | Inhaltsindex | tsvector | | erweitert | Indizes: "attachments_pkey" PRIMARY KEY, btree (id) "Anhang1" btree (Elternteil) "Anhang2" btree (Transaktions-ID) "attachments3" btree (Eltern, Transaktions-ID) "contentindex_idx" gin (contentindex) Hat OIDs: nein
Ich kann die Datenbank sehr schnell (<1s) selbst abfragen, mit einer Abfrage wie:
select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');
Wenn RT jedoch eine Abfrage ausführt, die eine Volltextindexsuche für dieselbe Tabelle durchführen soll, dauert der Abschluss normalerweise Hunderte von Sekunden. Die Ausgabe der Abfrageanalyse lautet wie folgt:
Abfrage
SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);
EXPLAIN ANALYZE
Ausgabe
Abfrageplan -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------- Aggregat (Kosten = 51210.60..51210.61 Zeilen = 1 Breite = 4) (tatsächliche Zeit = 477778.806..477778.806 Zeilen = 1 Schleifen = 1) -> Verschachtelte Schleife (Kosten = 0,00..51210,57 Zeilen = 15 Breite = 4) (tatsächliche Zeit = 17943,986..477775,174 Zeilen = 4197 Schleifen = 1) -> Verschachtelte Schleife (Kosten = 0,00..40643.08 Zeilen = 6507 Breite = 8) (tatsächliche Zeit = 8.526..20610.380 Zeilen = 1714818 Schleifen = 1) -> Seq Scan auf Tickets main (Kosten = 0,00..9818,37 Zeilen = 598 Breite = 8) (tatsächliche Zeit = 0,008..256,042 Zeilen = 96990 Schleifen = 1) Filter: (((Status) :: Text 'gelöscht' :: Text) UND (id = effektive ID) UND ((Typ) :: Text = 'Ticket' :: Text)) -> Index-Scan mit Transaktionen1 für Transaktionen Transaktionen_1 (Kosten = 0,00..51,36 Zeilen = 15 Breite = 8) (tatsächliche Zeit = 0,102..0,202 Zeilen = 18 Schleifen = 96990) Index Cond: (((Objekttyp) :: text = 'RT :: Ticket' :: Text) AND (objectid = main.id)) -> Index-Scan mit Anhängen2 für Anhänge Anhang_2 (Kosten = 0,00..1,61 Zeilen = 1 Breite = 4) (tatsächliche Zeit = 0,266..0,266 Zeilen = 0 Schleifen = 1714818) Index Cond: (transactionid = transaction_1.id) Filter: (contentindex @@ plago_tsquery ('frobnicate' :: text)) Gesamtlaufzeit: 477778,883 ms
Soweit ich das beurteilen kann, scheint das Problem darin zu bestehen, dass nicht der für contentindex
field ( contentindex_idx
) erstellte Index verwendet wird , sondern ein Filter für eine große Anzahl übereinstimmender Zeilen in der Anhangstabelle ausgeführt wird. Die ANALYZE
Zeilenanzahl in der EXPLAIN- Ausgabe scheint auch nach einer kürzlichen Zeit sehr ungenau zu sein : geschätzte Zeilen = 6507 tatsächliche Zeilen = 1714818.
Ich bin mir nicht sicher, wohin ich als nächstes gehen soll.
Antworten:
Dies kann auf tausend und eine Weise verbessert werden, dann sollte es eine Frage von Millisekunden sein .
Bessere Fragen
Dies ist nur Ihre Abfrage, die mit Aliasnamen neu formatiert und etwas Rauschen entfernt wurde, um den Nebel zu beseitigen:
Das meiste Problem mit Ihrer Abfrage liegt in den ersten beiden Tabellen
tickets
undtransactions
, die in der Frage fehlen. Ich fülle mit fundierten Vermutungen.t.status
,t.objecttype
undtr.objecttype
sollte wahrscheinlich nicht seintext
, aberenum
oder möglicherweise ein sehr kleiner Wert, der auf eine Nachschlagetabelle verweist.EXISTS
Semi-JoinVorausgesetzt, es
tickets.id
ist der Primärschlüssel, sollte dieses umgeschriebene Formular viel billiger sein:Anstatt Zeilen mit zwei 1: n -Verknüpfungen zu multiplizieren
count(DISTINCT id)
, verwenden Sie eineEXISTS
Semi-Verknüpfung, um am Ende mehrere Übereinstimmungen zu reduzieren. Sobald die erste Übereinstimmung gefunden wurde und der letzteDISTINCT
Schritt veraltet ist, kann die Suche unterbrochen werden. Pro Dokumentation:Die Wirksamkeit hängt davon ab, wie viele Transaktionen pro Ticket und Anhänge pro Transaktion vorhanden sind.
Bestimmen Sie die Reihenfolge der Verknüpfungen mit
join_collapse_limit
Wenn Sie wissen , dass Ihr Suchbegriff für
attachments.contentindex
ist sehr selektiv - selektive als andere Bedingungen in der Abfrage (was wahrscheinlich der Fall für ‚frobnicate‘ ist, aber nicht für ‚Problem‘), können Sie die Reihenfolge des Joins erzwingen. Der Abfrageplaner kann die Selektivität bestimmter Wörter mit Ausnahme der häufigsten kaum beurteilen. Pro Dokumentation:Verwenden Sie diese
SET LOCAL
Option, um sie nur für die aktuelle Transaktion festzulegen.Die Reihenfolge der
WHERE
Bedingungen ist immer irrelevant. Hier ist nur die Reihenfolge der Verknüpfungen relevant.Oder verwenden Sie einen CTE, wie @jjanes in "Option 2" erklärt. für einen ähnlichen Effekt.
Indizes
B-Baum-Indizes
Nehmen Sie alle Bedingungen an
tickets
, die bei den meisten Abfragen identisch verwendet werden, und erstellen Sie einen Teilindex fürtickets
:Wenn eine der Bedingungen variabel ist, löschen Sie sie aus der
WHERE
Bedingung und stellen Sie stattdessen die Spalte als Indexspalte voran.Ein weiterer auf
transactions
:Die dritte Spalte dient nur zum Aktivieren von Nur-Index-Scans.
Da Sie diesen zusammengesetzten Index mit zwei ganzzahligen Spalten haben
attachments
:Dieser zusätzliche Index ist eine völlige Verschwendung . Löschen Sie ihn:
Einzelheiten:
GIN-Index
Fügen Sie
transactionid
Ihrem GIN-Index hinzu, um ihn viel effektiver zu machen. Dies kann eine weitere Silberkugel sein , da möglicherweise nur Index-Scans möglich sind, wodurch Besuche des großen Tisches vollständig vermieden werden.Sie benötigen zusätzliche Operatorklassen, die vom zusätzlichen Modul bereitgestellt werden
btree_gin
. Detaillierte Anleitung:4 Bytes aus einer
integer
Spalte machen den Index nicht viel größer. Zum Glück unterscheiden sich GIN-Indizes in einem entscheidenden Aspekt von B-Tree-Indizes. Pro Dokumentation:Meine kühne Betonung. Sie brauchen also nur den einen (großen und etwas teuren) GIN-Index.
Tabellendefinition
Bewegen Sie die
integer not null columns
nach vorne. Dies hat einige geringfügige positive Auswirkungen auf Speicher und Leistung. Spart in diesem Fall 4 - 8 Bytes pro Zeile.quelle
Option 1
Der Planer hat keinen Einblick in die wahre Natur der Beziehung zwischen EffectiveId und id und denkt daher wahrscheinlich an die Klausel:
wird viel selektiver sein als es tatsächlich ist. Wenn dies meiner Meinung nach so ist, ist EffectiveID fast immer gleich main.id, aber der Planer weiß das nicht.
Eine möglicherweise bessere Möglichkeit, diese Art von Beziehung zu speichern, besteht normalerweise darin, den NULL-Wert von EffectiveID so zu definieren, dass er "effektiv dasselbe wie id" bedeutet, und nur dann etwas darin zu speichern, wenn es einen Unterschied gibt.
Angenommen, Sie möchten Ihr Schema nicht neu organisieren, können Sie versuchen, es zu umgehen, indem Sie diese Klausel wie folgt umschreiben:
Der Planer könnte annehmen, dass das
between
weniger selektiv ist als eine Gleichheit, und das könnte ausreichen, um es aus seiner aktuellen Falle herauszuholen.Option 2
Ein anderer Ansatz ist die Verwendung eines CTE:
Dies zwingt den Planer, ContentIndex als Selektivitätsquelle zu verwenden. Sobald dies erzwungen wird, sehen die irreführenden Spaltenkorrelationen auf dem Tickets-Tisch nicht mehr so attraktiv aus. Wenn jemand nach "Problem" und nicht nach "Frobnikat" sucht, kann dies natürlich nach hinten losgehen.
Option 3
Um die Schätzungen für fehlerhafte Zeilen weiter zu untersuchen, sollten Sie die folgende Abfrage in allen 2 ^ 3 = 8 Permutationen der verschiedenen AND-Klauseln ausführen, die auskommentiert werden. Dies hilft herauszufinden, woher die schlechte Schätzung kommt.
quelle