Ich habe eine Tabelle mit 7,2 Millionen Tupeln, die so aussieht:
table public.methods
column | type | attributes
--------+-----------------------+----------------------------------------------------
id | integer | not null DEFAULT nextval('methodkey'::regclass)
hash | character varying(32) | not null
string | character varying | not null
method | character varying | not null
file | character varying | not null
type | character varying | not null
Indexes:
"methods_pkey" PRIMARY KEY, btree (id)
"methodhash" btree (hash)
Jetzt möchte ich einige Werte auswählen, aber die Abfrage ist unglaublich langsam:
db=# explain
select hash, string, count(method)
from methods
where hash not in
(select hash from nostring)
group by hash, string
order by count(method) desc;
QUERY PLAN
----------------------------------------------------------------------------------------
Sort (cost=160245190041.10..160245190962.07 rows=368391 width=182)
Sort Key: (count(methods.method))
-> GroupAggregate (cost=160245017241.77..160245057764.73 rows=368391 width=182)
-> Sort (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
Sort Key: methods.hash, methods.string
-> Seq Scan on methods (cost=0.00..160243305942.27 rows=3683905 width=182)
Filter: (NOT (SubPlan 1))
SubPlan 1
-> Materialize (cost=0.00..41071.54 rows=970636 width=33)
-> Seq Scan on nostring (cost=0.00..28634.36 rows=970636 width=33)
Die hash
Spalte ist der MD5-Hash von string
und hat einen Index. Mein Problem ist also, dass die gesamte Tabelle nach ID und nicht nach Hash sortiert ist. Es dauert also eine Weile, sie zuerst zu sortieren und dann zu gruppieren.
Die Tabelle nostring
enthält nur eine Liste von Hashes, die ich nicht haben möchte. Aber ich brauche beide Tabellen, um alle Werte zu haben. Es ist also keine Option, diese zu löschen.
Zusätzliche Informationen: Keine der Spalten darf null sein (wie in der Tabellendefinition festgelegt) und ich verwende postgresql 9.2.
NULL
Werte in der Spaltemethod
? Gibt es Duplikate aufstring
?Antworten:
Die Antwort von
LEFT JOIN
in @ dezso sollte gut sein. Ein Index ist jedoch (per se) kaum sinnvoll, da die Abfrage ohnehin die gesamte Tabelle lesen muss - mit Ausnahme von Index-Scans in Postgres 9.2+ und günstigen Bedingungen, siehe unten.Führen Sie
EXPLAIN ANALYZE
die Abfrage aus. Mehrmals, um Kasseneffekte und Lärm auszuschließen. Vergleichen Sie die besten Ergebnisse.Erstellen Sie einen mehrspaltigen Index, der Ihrer Suchanfrage entspricht:
Warten? Nachdem ich sagte, ein Index würde nicht helfen? Nun, wir brauchen es zum
CLUSTER
Tisch:Erneut ausführen
EXPLAIN ANALYZE
. Noch schneller? Es sollte sein.CLUSTER
ist eine einmalige Operation zum Umschreiben der gesamten Tabelle in der Reihenfolge des verwendeten Index. Es ist auch effektiv einVACUUM FULL
. Wenn Sie sicher sein möchten, führen Sie einen Vortest mitVACUUM FULL
allein durch, um zu sehen, was darauf zurückzuführen ist.Wenn in Ihrer Tabelle viele Schreibvorgänge ausgeführt werden, nimmt der Effekt mit der Zeit ab. Planen Sie
CLUSTER
außerhalb der Geschäftszeiten, um den Effekt wiederherzustellen. Die Feinabstimmung hängt von Ihrem genauen Anwendungsfall ab. Das Handbuch zuCLUSTER
.CLUSTER
ist ein eher grobes Werkzeug, braucht eine exklusive Sperre auf dem Tisch. Wenn Sie sich das nicht leisten können, überlegen Sie,pg_repack
was Sie auch ohne exklusive Sperre tun können. Mehr in dieser späteren Antwort:Wenn der Prozentsatz der
NULL
Werte in der Spaltemethod
hoch ist (mehr als ~ 20 Prozent, abhängig von der tatsächlichen Zeilengröße), sollte ein Teilindex helfen:(In Ihrem späteren Update werden Ihre Spalten als
NOT NULL
nicht zutreffend angezeigt.)Wenn Sie PostgreSQL 9.2 oder höher ausführen (wie von @deszo kommentiert ), können die angezeigten Indizes nützlich sein, ohne
CLUSTER
dass der Planer nur Index-Scans verwenden kann . Nur unter günstigen Bedingungen anwendbar: Keine Schreibvorgänge, die sich auf die Sichtbarkeitskarte auswirken würden, da die letzteVACUUM
und alle Spalten in der Abfrage vom Index abgedeckt werden müssen. Grundsätzlich können schreibgeschützte Tabellen dies jederzeit verwenden, während stark geschriebene Tabellen begrenzt sind. Weitere Details im Postgres Wiki.Der oben erwähnte Teilindex könnte in diesem Fall sogar noch nützlicher sein.
Wenn die Spalte hingegen keine
NULL
Werte enthältmethod
, sollten Sie1.) diese definieren
NOT NULL
und2.)
count(*)
stattdessen verwendencount(method)
, das ist etwas schneller und funktioniert auch ohneNULL
Werte.Wenn Sie diese Abfrage häufig aufrufen müssen und die Tabelle schreibgeschützt ist, erstellen Sie eine
MATERIALIZED VIEW
.Exotischer Feinschliff: Ihre Tabelle hat einen Namen
nostring
, scheint jedoch Hashes zu enthalten. Wenn Sie Hashes anstelle von Zeichenfolgen ausschließen, besteht die Möglichkeit, dass Sie mehr Zeichenfolgen als beabsichtigt ausschließen. Sehr unwahrscheinlich, aber möglich.quelle
Willkommen bei DBA.SE!
Sie können versuchen, Ihre Anfrage wie folgt neu zu formulieren:
oder eine andere Möglichkeit:
NOT IN
ist eine typische Leistungssenke, da es schwierig ist, einen Index damit zu verwenden.Dies kann durch Indizes weiter verbessert werden. Ein Index über
nostring.hash
sieht nützlich aus. Aber zuerst: Was bekommen Sie jetzt? (Es wäre besser, die Ausgabe von zu sehenEXPLAIN ANALYZE
da die Kosten selbst nicht die Zeit angeben, die die Operationen in genommen haben.)quelle
EXPLAIN ANALYZE
.Da Hash ein md5 ist, können Sie wahrscheinlich versuchen, es in eine Zahl umzuwandeln: Sie können es als Zahl speichern oder einfach einen Funktionsindex erstellen, der diese Zahl in einer unveränderlichen Funktion berechnet.
Andere haben bereits eine Funktion pl / pgsql erstellt, die einen md5-Wert (teilweise) von Text in Zeichenfolge konvertiert. Ein Beispiel finden Sie unter /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql
Ich glaube, dass Sie beim Durchsuchen des Index wirklich viel Zeit mit dem Vergleichen von Zeichenfolgen verbringen. Wenn Sie es schaffen, diesen Wert als Zahl zu speichern, sollte er wirklich schneller sein.
quelle
Ich bin häufig auf dieses Problem gestoßen und habe einen einfachen zweiteiligen Trick entdeckt.
Erstellen Sie einen Teilstring-Index für den Hash-Wert: (7 ist normalerweise eine gute Länge.)
create index methods_idx_hash_substring ON methods(substring(hash,1,7))
Lassen Sie Ihre Suchen / Verknüpfungen eine Teilzeichenfolgenübereinstimmung enthalten, sodass der Abfrageplaner darauf hingewiesen wird, den Index zu verwenden:
alt:
WHERE hash = :kwarg
Neu:
WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))
Sie sollten auch einen Index für die Rohdaten haben
hash
.Das Ergebnis ist (normalerweise), dass der Planer zuerst den Teilzeichenfolgenindex konsultiert und die meisten Zeilen aussortiert. Dann wird der gesamte 32-Zeichen-Hash mit dem entsprechenden Index (oder der entsprechenden Tabelle) abgeglichen. Dieser Ansatz hat 800-ms-Abfragen für mich auf 4 reduziert.
quelle