Das Hinzufügen einer nullbaren Spalte zur Tabelle kostet mehr als 10 Minuten

11

Ich habe Probleme, einer Tabelle eine neue Spalte hinzuzufügen.
Ich habe ein paar Mal versucht, es auszuführen, aber nach mehr als 10 Minuten habe ich beschlossen, die Abfrage wegen der Sperrzeit abzubrechen.

ALTER TABLE mytable ADD mycolumn VARCHAR(50);

Nützliche Informationen:

  • PostgreSQL-Version: 9.1
  • Anzahl der Zeilen: ~ 250K
  • Anzahl der Spalten: 38
  • Anzahl der nullbaren Spalten: 32
  • Anzahl der Einschränkungen: 5 (1 PK, 3 FK, 1 EINZIGARTIG)
  • Anzahl der Indizes: 1
  • Betriebssystemtyp: Debian Squeeze 64

Ich fand interessante Informationen darüber, wie PostgreSQL nullfähige Spalten verwaltet (über HeapTupleHeader).

Meine erste Vermutung ist, dass MAXALIGNHeapTupleHeader 4 Bytes lang ist, da diese Tabelle bereits 32 nullfähige Spalten mit 8 Bit enthält (nicht verifiziert, und ich weiß nicht, wie das geht).

Das Hinzufügen einer neuen nullfähigen Spalte erfordert möglicherweise eine Aktualisierung von HeapTupleHeader in jeder Zeile, um neue 8-Bit-Spalten hinzuzufügen MAXALIGN, was zu Leistungsproblemen führen kann.

Also habe ich versucht, eine der nullbaren Spalten (die in Wirklichkeit nicht wirklich nullbar ist) zu ändern, um die Anzahl der nullbaren Spalten auf 31 zu verringern und zu überprüfen, ob meine Vermutung wahr sein könnte.

ALTER TABLE mytable ALTER myothercolumn SET NOT NULL;

Leider dauert diese Änderung auch sehr lange, mehr als 5 Minuten, so dass ich sie auch abgebrochen habe.

Haben Sie eine Vorstellung davon, was diese Leistungskosten verursachen könnte?

Matthieu Verrecchia
quelle
1
Nun, ich kann Ihnen einen Teil davon sagen: Wenn Sie einen Spaltentyp in einen anderen Typ ändern, der nicht binär kompatibel ist, wird tatsächlich eine neue Spalte erstellt, die Daten kopiert und die alte Spalte als gelöscht festgelegt. Jedoch SET NOT NULLnicht die Art ändern, es fügt nur eine Einschränkung - aber die Einschränkung muss geprüft gegen den Tisch, und das erfordert einen vollständigen Tabellenscan. 9.4 verbessert einige dieser Fälle, indem es schwächere Sperren verwendet, aber es ist immer noch ziemlich schwer.
Craig Ringer
1
Bevor Sie den Verdacht haben, dass die Leistung langsam ist, müssen Sie sicherstellen, dass die ALTER TABLE nicht nur auf eine Sperre wartet. Erwähnen Sie es in der Frage, wenn Sie überprüft haben.
Daniel Vérité
Danke Craig und Daniel. Wenn ich den Befehl alter ausführe, wird er in pg_stat_activity mit dem Warten auf "true" angezeigt. Ich nehme an, das bedeutet, dass er auf eine Sperre wartet! Ist es der gute Weg, um zu überprüfen? Übrigens, bevor diese Änderung ausgeführt wird, läuft alles gut, aber einige Sekunden nach dem Start wächst die Anzahl der Sperren
Versuchen Sie die Abfrage unter wiki.postgresql.org/wiki/Lock_dependency_information, um eine bessere Ansicht zu erhalten. Entweder haben Sie verweilende Transaktionen, die das Festschreiben vergessen haben, oder Sie haben starke Aktivitäten mit dieser Tabelle, die sie immer beschäftigt hält.
Daniel Vérité
Könnte bei dba.SE besser passen.
Erwin Brandstetter

Antworten:

8

Hier gibt es einige Missverständnisse:

Die Null-Bitmap ist nicht Teil des Heap-Tupel-Headers. Pro Dokumentation:

Es gibt einen Header mit fester Größe (der auf den meisten Computern 23 Byte belegt), gefolgt von einer optionalen Null-Bitmap ...

Ihre 32 nullbaren Spalten sind aus zwei Gründen nicht verdächtig:

  • Die Null-Bitmap wird pro Zeile hinzugefügt , und zwar nur, wenn mindestens ein tatsächlicher NULLWert in der Zeile vorhanden ist. Nullable Spalten haben keine direkte Auswirkung, nur tatsächliche NULLWerte. Wenn die Null-Bitmap zugewiesen ist, wird sie immer vollständig zugewiesen (alles oder nichts). Die tatsächliche Größe der Null-Bitmap beträgt 1 Bit pro Spalte, aufgerundet auf das nächste Byte . Gemäß aktuellem Quellencode:

    #define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
  • Die Null-Bitmap wird nach dem Heap-Tupel-Header zugewiesen, gefolgt von einer optionalen OID und anschließend Zeilendaten. Der Beginn einer OID oder von Zeilendaten wird t_hoffim Header durch angezeigt . Quellcode pro Kommentar :

    Beachten Sie, dass t_hoff ein Vielfaches von MAXALIGN sein muss.

  • Nach dem Heap-Tupel-Header befindet sich ein freies Byte, das 23 Bytes belegt. Die Null-Bitmap für Zeilen mit bis zu 8 Spalten ist also effektiv ohne zusätzliche Kosten. Mit der 9. Spalte in der Tabelle werden t_hoffweitere MAXALIGN(normalerweise 8) Bytes erweitert, um weitere 64 Spalten bereitzustellen. Die nächste Grenze wäre also 72 Spalten.

So zeigen Sie Steuerinformationen eines PostgreSQL-Datenbankclusters (inkl. MAXALIGN) An, Beispiel für eine typische Installation von Postgres 9.3 auf einem Debian-Computer:

    sudo /usr/lib/postgresql/9.3/bin/pg_controldata /var/lib/postgresql/9.3/main

Ich habe die Anweisungen in der von Ihnen zitierten Antwort aktualisiert .

ALTER TABLEAbgesehen davon sind 250 KB wirklich nicht so viel und würden auf jedem halbwegs anständigen Computer eine Frage von Sekunden sein (es sei denn, die Zeilen sind ungewöhnlich groß) , selbst wenn Ihre Anweisung ein Umschreiben einer ganzen Tabelle auslöst (was wahrscheinlich der Fall ist, wenn ein Datentyp geändert wird). . 10 Minuten oder mehr weisen auf ein völlig anderes Problem hin. Ihre Aussage wartet höchstwahrscheinlich darauf, eine Sperre für den Tisch zu bekommen.

Die wachsende Anzahl von Einträgen in pg_stat_activitybedeutet offenere Transaktionen - zeigt an, dass gleichzeitig (höchstwahrscheinlich) gleichzeitig auf die Tabelle zugegriffen werden muss, bis der Vorgang abgeschlossen ist.

Ein paar Aufnahmen im Dunkeln

Suchen Sie nach einem möglichen Aufblähen des Tisches, versuchen Sie es mit einem sanften VACUUM mytableoder aggressiveren Vorgang VACUUM FULL mytable- bei dem möglicherweise dieselben Parallelitätsprobleme auftreten, da dieses Formular auch eine exklusive Sperre erhält. Sie könnten stattdessen pg_repack versuchen ...

Ich würde zunächst mögliche Probleme mit Indizes, Triggern, Fremdschlüsseln oder anderen Einschränkungen untersuchen, insbesondere solche, die die Spalte betreffen. Insbesondere könnte ein beschädigter Index beteiligt sein? Probieren Sie REINDEX TABLE mytable;oder DROPalle aus und fügen Sie sie anschließend ALTER TABLE in derselben Transaktion erneut hinzu .

Versuchen Sie, den Befehl nachts oder immer dann auszuführen, wenn nicht viel Last vorhanden ist.

Eine Brute-Force-Methode wäre, den Zugriff auf den Server zu beenden und es dann erneut zu versuchen:

Ein Upgrade auf die aktuelle Version oder insbesondere auf die kommende Version 9.4 kann hilfreich sein, ohne dass Sie dies feststellen können . Es wurden verschiedene Verbesserungen für große Tabellen und für das Sperren von Details vorgenommen. Aber wenn in Ihrer Datenbank etwas kaputt ist, sollten Sie das wahrscheinlich zuerst herausfinden.

Erwin Brandstetter
quelle
2
Es ist mit ziemlicher Sicherheit Schlösser. Als Test können Sie jedoch jederzeit eine Kopie der Tabelle erstellen und versuchen, diese zu ändern. Wenn das nicht sehr lange dauert, wissen Sie, dass nicht die eigentliche Änderung das Problem ist.
Danke für die Erklärungen Erwin. Ich denke du hast recht, es scheint ein Schlossproblem zu sein. Wenn ich pg_stat_activity überprüfe, kann ich sehen, dass mein ALTER ein "Warten" wahr hat. Was ich nicht herausfinden kann, ist, warum der ALTER die Sperre für die Tabelle nicht erhalten kann, denn selbst wenn ich keine laufende Abfrage finde, scheint es, dass er sie nicht bekommen kann. Sobald mein ALTER ausgeführt wird, warten alle anderen Abfragen darauf, dass er beendet wird. Die Aktivität scheint also anzuzeigen, dass ALTER alle anderen Abfragen sperrt, zeigt aber auch an, dass ALTER die Sperre nicht erhalten hat. Ich denke, es gibt etwas, das ich nicht gut verstehe !?
@MatthieuVerrecchia: Hast du den von Richard vorgeschlagenen Test ausprobiert?
Erwin Brandstetter
1
Ich habe gerade meine Tabelle auf eine neue geklont (mit pg_dump -> pg_sql). Die neue Spalte wird in 50 ms korrekt hinzugefügt, was das Sperrproblem bestätigt. Übrigens, verstehe immer noch nicht, warum ALTER nicht mit wirklich Standard-DB-Aktivitäten gesperrt werden kann.
1
@ErwinBrandstetter Ich bin Ihren Vorschlägen gefolgt und habe ein VAKUUM und dann einen REINDEX ausprobiert. Der REINDEX wurde ebenfalls blockiert, da er auch keine Sperre erhalten konnte. Nach einigen Untersuchungen war das Problem einfacher als gedacht. Es blieb noch eine Woche <IDLE> mit einer geöffneten Transaktion. Das Problem ist gelöst, danke Informationen waren für alles sehr nützlich.