PostgreSQL-Teilindex wird beim Erstellen einer Tabelle mit vorhandenen Daten nicht verwendet

7

In PostgreSQL 9.3 versuche ich, einen effizienten Index für eine selten verwendete boolesche Spalte (0,00001% aller Datensätze) zu erstellen. Zu diesem Zweck habe ich diesen Beitrag auf SO entdeckt: https://stackoverflow.com/a/12026593/808921

Ich versuche, die von Erwin Brandstetter empfohlene Funktion "Teilindex" von PostgreSQL zu nutzen. Ich habe bereits eine Tabelle mit ein paar Millionen Datensätzen und möchte den Index wie folgt zu dieser Tabelle hinzufügen:

CREATE INDEX schema_defs_deprovision ON schema_defs (deprovision) 
WHERE deprovision = 0;

(Die überwiegende Mehrheit der Aufzeichnungen wird haben deprovision = 1)

Das Problem ist, dass PostgreSQL, wenn ich versuche, diesen Index mit den einfachsten Abfragen zu verwenden, so tut, als ob er nicht vorhanden wäre:

explain select * from schema_defs where deprovision = 0;

Seq Scan on schema_defs (cost=0.00..1.05 rows=1 width=278)
Filter: (deprovision = 0)

Das wirklich Seltsame ist, dass ich festgestellt habe, dass wenn dieser Index erstellt wird, bevor Daten in der Tabelle vorhanden sind, er tatsächlich einwandfrei funktioniert. Glaubst du mir nicht? Hier sind einige SQL Fiddle-Einträge, die dies beweisen:

Teilindex nach Einfügungen erstellt (Index funktioniert nicht)

Teilindex vor Einfügungen erstellt (Index funktioniert ordnungsgemäß)

Erweitern Sie in beiden Fällen einfach den Link "Ausführungsplan anzeigen", um zu sehen, wovon ich spreche.

Meine Frage lautet also: Was muss ich tun, damit PostgreSQL einen Teilindex für eine Tabelle verwendet, in der sich Daten befanden, bevor der Index erstellt wurde?

Übrigens bin ich auch der Entwickler von SQL Fiddle und diese Frage hängt mit einer neuen Entwicklungsanstrengung zusammen, die ich dafür mache.

Jake Feasel
quelle
1
Als regelmäßiger Nutzer Ihrer Website bin ich besonders interessiert. Wenn die Tabellendefinition in der Geige das ist, was Sie tatsächlich verwenden, habe ich möglicherweise ein paar Hinweise, um sie zu verbessern. Stellen Sie bei Interesse eine weitere Frage mit der vollständigen Definition.
Erwin Brandstetter
Entschuldigen Sie die Verzögerung Erwin - es gibt einige weitere Details, die ich nachträglich entdeckt habe, und mit Ihrer Erinnerung werde ich mich diesen anschließen. Ihre Antwort war sehr hilfreich, danke!
Jake Feasel

Antworten:

9

Ausführen, ANALYZEnachdem der Index hinzugefügt wurde. Und stellen Sie sicher , dass die Spalte deprovision hat Statistiken. Wie überprüfe ich?

Grundlegende Statistiken in pg_class:

SELECT relname, relkind, reltuples, relpages
FROM   pg_class
WHERE  oid = 'schema_defs'::regclass;

Datenhistogramme pro Spalte in pg_stats( pg_statistics):

SELECT attname, inherited, n_distinct
     , array_to_string(most_common_vals, E'\n') AS most_common_vals
FROM   pg_stats
WHERE  tablename = 'schema_defs'
AND    attname = 'deprovision';

Das Handbuch:

Der PostgreSQL-Abfrageplaner stützt sich auf statistische Informationen zum Inhalt von Tabellen, um gute Pläne für Abfragen zu erstellen. Diese Statistiken werden vom ANALYZEBefehl erfasst, der von sich aus oder als optionaler Schritt aufgerufen werden kann VACUUM. Es ist wichtig, einigermaßen genaue Statistiken zu haben, da sonst eine schlechte Auswahl der Pläne die Datenbankleistung beeinträchtigen kann.

Wenn der Autovacuum-Daemon aktiviert ist, gibt er automatisch ANALYZE Befehle aus, wenn sich der Inhalt einer Tabelle ausreichend geändert hat. Administratoren ziehen es jedoch möglicherweise vor, sich auf manuell geplante ANALYZEVorgänge zu verlassen, insbesondere wenn bekannt ist, dass die Aktualisierungsaktivität für eine Tabelle die Statistik "interessanter" Spalten nicht beeinflusst. Der Daemon plant ANALYZEausschließlich in Abhängigkeit von der Anzahl der eingefügten oder aktualisierten Zeilen. Es ist nicht bekannt, ob dies zu bedeutenden statistischen Änderungen führen wird.

In Ihrem Fall würde die Analyse nur einer Spalte die Aufgabe erfüllen:

ANALYZE table_name (deprovision);

Wenn Sie gerade dabei sind, macht es keinen Sinn, den Index für die Spalte zu haben deprovision. In Anbetracht des Prädikats enthält WHERE deprovision = 0es keine zusätzlichen Informationen. Sie können auch einen konstanten Ausdruck verwenden:

CREATE INDEX schema_defs_deprovision ON schema_defs ((true)) 
WHERE deprovision = 0;

Nur ein Proof of Concept. Dies wäre nicht nützlicher. In diesem speziellen Fall würden Sie einen Index nicht benötigen Spalte überhaupt, aber Sie müssen mindestens eine Spalte oder einen Ausdruck zur Verfügung stellen. Verwenden Sie daher den Primärschlüssel (da er sich nicht ändert und ohnehin indiziert ist, führen Sie keine weiteren Einschränkungen / Gemeinkosten ein) oder eine andere kleine Spalte (<= 8 Byte), die für Abfragen nützlich sein kann.

CREATE INDEX schema_defs_deprovision ON schema_defs (id) 
WHERE deprovision = 0;

sqlfiddle.com

Die Demo-Geigen sind irreführend .

Teilindex vor Einfügungen erstellt (Index funktioniert ordnungsgemäß)

Ihre Demo-Tabelle hat nur 4 Zeilen. Postgres sollte nicht mit dem Index sein. Ein ähnliches Problem, genau umgekehrt. Postgres verfügt nicht unmittelbar nach dem Erstellen über Statistiken in der Tabelle - bis zum ersten Durchlauf von ANALYZE. Dann weiß es , dass es nur 4 Zeilen gibt und den Index nicht mehr berührt.
Warum funktioniert es bei Ihrer zweiten Demo richtig? Das Handbuch:

Aus Effizienzgründen reltuplesund relpageswerden nicht im laufenden Betrieb aktualisiert und enthalten daher normalerweise etwas veraltete Werte. Sie zeichnen sich durch aktualisiert VACUUM, ANALYZEund ein paar DDL - Befehle wie CREATE INDEX .

Meine kühne Betonung. Wenn Sie den Index nach dem Einfügen von Zeilen erstellen , werden diese grundlegenden Statistiken pg_classaktualisiert. Aber nur diese, nicht die detaillierten Statistiken in pg_statistic:

Einträge in pg_statisticwerden von den aktualisierten ANALYZEund VACUUM ANALYZE Befehlen und werden immer nähern , auch wenn frisch aktualisiert.

Damit Postgres den Teilindex verwendet (insbesondere in seiner ursprünglichen Form, die für nichts anderes nützlich ist), benötigen Sie auch das Datenhistogramm pg_statistic, um den Abfrageplaner darüber zu informieren, dass dies deprovision = 0tatsächlich ein seltener Fall ist. Daher lohnt es sich, den Index zu verwenden.

Dafür sorgt Autovacuum . Es plant VACUUMund ANALYZEautomatisch. Zwischen den Schreibvorgängen in die Tabelle und dem nächsten ANALYZEDurchlauf liegt jedoch ein Zeitrahmen (abhängig von den Einstellungen und der Auslastung) . Wenn Sie Abfragen unmittelbar nach der Tabellenerstellung oder Änderungen an der Tabelle ausführen, werden diese letzten Änderungen noch nicht in der Statistik berücksichtigt. Es ist egal, ob dies die Statistik nicht in relevanter Weise ändert. Wenn dies beispielsweise nach einer großen INSERToder unmittelbar nach der Tabellenerstellung der Fall ist , führen Sie diese ANALYZEmanuell aus, um die richtigen Abfragepläne zu erhalten.

Beachten Sie, dass temporäre Tabellen überhaupt nicht von Autovakuum abgedeckt werden. Sie müssen diese immer ANALYZEmanuell ausführen , wenn Sie sie benötigen:

Ich weiß nicht, wie Sie das Autovakuum konfiguriert haben und ob / wann Sie es ANALYZEmanuell ausführen . Aber ich habe in der Vergangenheit festgestellt, dass sqlfiddle aufgrund fehlender / veralteter Statistiken irreführend sein kann.

Mich würde sehr interessieren, wie ANALYZEhinter den Vorhängen auf sqlfiddle gehandhabt wird. Es ist vielleicht am besten, nichts Besonderes zu tun, aber einige Informationen wären willkommen. Vielleicht eine einfache Webseite pro verfügbarer RDBMS-Version?

Demo

Ich habe eine SQL-Geige erstellt , um die Auswirkungen CREATE INDEXundANALYZE auf die verschiedenen Statistiken zu demonstrieren .

Die Effekte zeigen sich (zumindest) beim ersten Lauf für mich. Bei späteren Läufen möglicherweise nicht reproduzierbar. Sie müssten ein neues Schema erstellen und erneut ausführen.

Zuerst sehen wir keine grundlegenden Statistiken in pg_class:

relname      reltuples  relpages
schema_defs  0          0

Noch irgendwelche Einträge für deprovisionin pg_statisticsüberhaupt (kein Ergebnis).
Postgres hat keine Ahnung, was in der Tabelle steht, und verwendet standardmäßig den Index - was eine schlechte Wahl ist!

Danach sehen CREATE INDEXwir grundlegende Statistiken, aber immer noch kein Datenhistogramm in pg_statistics.

Nachdem ANALYZEwir beide gesehen haben.

Bei korrekter Statistik verwendet Postgres jetzt einen sequentiellen Scan (gute Wahl, selbst wenn ein Index vorhanden ist - dies wäre für so wenige Zeilen teurer).

Erwin Brandstetter
quelle