MVCC in PostgreSQL-6. Vakuum

Wir begannen mit Problemen im Zusammenhang mit der Isolation , machten einen Exkurs über die Datenstruktur auf niedriger Ebene , diskutierten dann Zeilenversionen und beobachteten, wie Datenschnappschüsse aus Zeilenversionen erhalten werden.

Das letzte Mal haben wir über HOT-Updates und In-Page-Staubsaugen gesprochen, und heute werden wir zu einer bekannten Vakuum-Vulgaris übergehen . Wirklich, es ist bereits so viel darüber geschrieben worden, dass ich kaum etwas Neues hinzufügen kann, aber die Schönheit eines vollständigen Bildes erfordert Opfer. Also haltet euch geduldig.

Vakuum


Was macht Vakuum?


Der In-Page-Unterdruck arbeitet schnell, gibt jedoch nur einen Teil des Speicherplatzes frei. Es funktioniert innerhalb einer Tabellenseite und berührt keine Indizes.

Das "normale" Grundvakuum wird mit dem Befehl VACUUM erzeugt, und wir werden es nur "Vakuum" nennen ("Autovakuum" für eine separate Diskussion belassen).

Vakuum bearbeitet also den gesamten Tisch. Es saugt nicht nur tote Tupel weg, sondern verweist auch auf sie aus allen Indizes.

Das Staubsaugen erfolgt gleichzeitig mit anderen Aktivitäten im System. Die Tabelle und die Indizes können regelmäßig sowohl zum Lesen als auch zum Aktualisieren verwendet werden (die gleichzeitige Ausführung von Befehlen wie CREATE INDEX, ALTER TABLE und einigen anderen ist jedoch nicht möglich).

Es werden nur die Tabellenseiten durchgesehen, auf denen einige Aktivitäten stattgefunden haben. Um sie zu erkennen, wird die Sichtbarkeitskarte verwendet (um Sie daran zu erinnern, verfolgt die Karte die Seiten, die ziemlich alte Tupel enthalten, die auf jeden Fall in allen Datenschnappschüssen sichtbar sind). Es werden nur die Seiten verarbeitet, die von der Sichtbarkeitskarte nicht erfasst werden, und die Karte selbst wird aktualisiert.

Die Zuordnung des freien Speicherplatzes wird dabei ebenfalls aktualisiert, um den zusätzlichen freien Speicherplatz auf den Seiten widerzuspiegeln.

Wie üblich erstellen wir eine Tabelle:

=> CREATE TABLE vac( id serial, s char(100) ) WITH (autovacuum_enabled = off); => CREATE INDEX vac_s ON vac(s); => INSERT INTO vac(s) VALUES ('A'); => UPDATE vac SET s = 'B'; => UPDATE vac SET s = 'C'; 

Wir verwenden den Parameter autovacuum_enabled , um den Autovakuum-Prozess auszuschalten. Wir werden es das nächste Mal diskutieren, und jetzt ist es für unsere Experimente entscheidend, dass wir das Staubsaugen manuell steuern.

Die Tabelle enthält jetzt drei Tupel, von denen jedes aus dem Index referenziert wird:

 => SELECT * FROM heap_page('vac',0); 
  ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+----------+-----+-----+-------- (0,1) | normal | 4000 (c) | 4001 (c) | | | (0,2) (0,2) | normal | 4001 (c) | 4002 | | | (0,3) (0,3) | normal | 4002 | 0 (a) | | | (0,3) (3 rows) 

 => SELECT * FROM index_page('vac_s',1); 
  itemoffset | ctid ------------+------- 1 | (0,1) 2 | (0,2) 3 | (0,3) (3 rows) 

Nach dem Staubsaugen werden tote Tupel abgesaugt, und es bleibt nur ein lebendes Tupel übrig. Und nur eine Referenz bleibt im Index:

 => VACUUM vac; => SELECT * FROM heap_page('vac',0); 
  ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+-------+-----+-----+-------- (0,1) | unused | | | | | (0,2) | unused | | | | | (0,3) | normal | 4002 (c) | 0 (a) | | | (0,3) (3 rows) 
 => SELECT * FROM index_page('vac_s',1); 
  itemoffset | ctid ------------+------- 1 | (0,3) (1 row) 

Beachten Sie, dass die ersten beiden Zeiger den Status "unbenutzt" anstelle von "tot" erhielten, den sie mit In-Page-Vakuum erhalten würden.

Über den Transaktionshorizont noch einmal


Wie erkennt PostgreSQL, welche Tupel als tot angesehen werden können? Wir haben das Konzept des Transaktionshorizonts bereits bei der Erörterung von Datenschnappschüssen angesprochen, aber es wird nicht schaden, eine so wichtige Angelegenheit zu wiederholen.

Beginnen wir noch einmal mit dem vorherigen Experiment.

 => TRUNCATE vac; => INSERT INTO vac(s) VALUES ('A'); => UPDATE vac SET s = 'B'; 

Bevor Sie die Zeile erneut aktualisieren, müssen Sie eine weitere Transaktion starten (aber nicht beenden). In diesem Beispiel wird die Ebene "Read Committed" verwendet, es muss jedoch eine echte (nicht virtuelle) Transaktionsnummer angegeben werden. Beispielsweise kann die Transaktion bestimmte Zeilen in einer beliebigen Tabelle ändern und sogar sperren, die nicht obligatorisch ist.

 | => BEGIN; | => SELECT s FROM t FOR UPDATE; 
 | s | ----- | FOO | BAR | (2 rows) 

 => UPDATE vac SET s = 'C'; 

Die Tabelle enthält drei Zeilen und der Index enthält drei Verweise. Was passiert nach dem Staubsaugen?

 => VACUUM vac; => SELECT * FROM heap_page('vac',0); 
  ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+----------+-----+-----+-------- (0,1) | unused | | | | | (0,2) | normal | 4005 (c) | 4007 (c) | | | (0,3) (0,3) | normal | 4007 (c) | 0 (a) | | | (0,3) (3 rows) 
 => SELECT * FROM index_page('vac_s',1); 
  itemoffset | ctid ------------+------- 1 | (0,2) 2 | (0,3) (2 rows) 

Es verbleiben zwei Tupel in der Tabelle: VACUUM hat entschieden, dass das (0,2) -Tupel noch nicht gesaugt werden kann. Der Grund liegt sicherlich im Transaktionshorizont der Datenbank, der in diesem Beispiel durch die nicht abgeschlossene Transaktion bestimmt wird:

 | => SELECT backend_xmin FROM pg_stat_activity WHERE pid = pg_backend_pid(); 
 | backend_xmin | -------------- | 4006 | (1 row) 

Wir können VACUUM bitten zu melden, was gerade passiert:

 => VACUUM VERBOSE vac; 
 INFO: vacuuming "public.vac" INFO: index "vac_s" now contains 2 row versions in 2 pages DETAIL: 0 index row versions were removed. 0 index pages have been deleted, 0 are currently reusable. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. INFO: "vac": found 0 removable, 2 nonremovable row versions in 1 out of 1 pages DETAIL: 1 dead row versions cannot be removed yet, oldest xmin: 4006 There were 1 unused item pointers. Skipped 0 pages due to buffer pins, 0 frozen pages. 0 pages are entirely empty. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. VACUUM 

Beachten Sie, dass:

  • 2 nonremovable row versions - zwei Tupel, die nicht gelöscht werden können, sind in der Tabelle enthalten.
  • 1 dead row versions cannot be removed yet - eine davon ist tot.
  • oldest xmin zeigt den aktuellen Horizont.

Lassen Sie uns die Schlussfolgerung wiederholen: Wenn eine Datenbank über langlebige Transaktionen verfügt (die nicht abgeschlossen wurden oder sehr lange ausgeführt werden), kann dies zu einer Aufblähung der Tabelle führen, unabhängig davon, wie oft gesaugt wird. Aus diesem Grund existieren OLTP- und OLAP-Workloads in einer PostgreSQL-Datenbank nur unzureichend zusammen: Berichte, die stundenlang ausgeführt werden, lassen aktualisierte Tabellen nicht ordnungsgemäß staubsaugen. Die Erstellung eines separaten Replikats für Berichtszwecke kann eine mögliche Lösung dafür sein.

Nach Abschluss einer offenen Transaktion verschiebt sich der Horizont und die Situation wird behoben:

 | => COMMIT; 

 => VACUUM VERBOSE vac; 
 INFO: vacuuming "public.vac" INFO: scanned index "vac_s" to remove 1 row versions DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s INFO: "vac": removed 1 row versions in 1 pages DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s INFO: index "vac_s" now contains 1 row versions in 2 pages DETAIL: 1 index row versions were removed. 0 index pages have been deleted, 0 are currently reusable. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. INFO: "vac": found 1 removable, 1 nonremovable row versions in 1 out of 1 pages DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 4008 There were 1 unused item pointers. Skipped 0 pages due to buffer pins, 0 frozen pages. 0 pages are entirely empty. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. VACUUM 

Jetzt verbleibt nur noch die neueste Live-Version der Zeile auf der Seite:

 => SELECT * FROM heap_page('vac',0); 
  ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+----------+-------+-----+-----+-------- (0,1) | unused | | | | | (0,2) | unused | | | | | (0,3) | normal | 4007 (c) | 0 (a) | | | (0,3) (3 rows) 

Der Index hat auch nur eine Zeile:

 => SELECT * FROM index_page('vac_s',1); 
  itemoffset | ctid ------------+------- 1 | (0,3) (1 row) 

Was passiert drinnen?


Das Staubsaugen muss die Tabelle und die Indizes gleichzeitig verarbeiten und dies tun, um die anderen Prozesse nicht zu sperren. Wie kann das gehen?

Alles beginnt mit der Scan-Heap- Phase (die bereits erwähnte Sichtbarkeitskarte wird berücksichtigt). Auf den gelesenen Seiten werden tote Tupel erkannt und ihre tid in ein spezielles Array geschrieben. Das Array wird im lokalen Speicher des Vakuumprozesses abgelegt, wo ihm die Bytes des Speichers maintenance_work_mem zugewiesen werden. Der Standardwert dieses Parameters beträgt 64 MB. Beachten Sie, dass die gesamte Speicherkapazität sofort zugewiesen wird und nicht nach Bedarf. Wenn die Tabelle jedoch nicht groß ist, wird weniger Speicher zugewiesen.

Dann erreichen wir entweder das Ende der Tabelle oder der für das Array zugewiesene Speicher ist vorbei. In beiden Fällen beginnt die Phase der Vakuumindizes . Zu diesem Zweck wird jeder für die Tabelle erstellte Index vollständig auf die Zeilen durchsucht, die auf die gespeicherten Tupel verweisen. Die gefundenen Zeilen werden von den Indexseiten entfernt.

Hier stellen wir uns dem Folgenden: Die Indizes haben noch keine Referenzen auf tote Tupel, während die Tabelle sie noch enthält. Und das steht im Widerspruch zu nichts: Wenn wir eine Abfrage ausführen, treffen wir entweder keine toten Tupel (mit Indexzugriff) oder lehnen sie bei der Sichtbarkeitskontrolle ab (beim Scannen der Tabelle).

Danach beginnt die Phase des Staubsaugens . Die Tabelle wird erneut gescannt, um die entsprechenden Seiten zu lesen, sie von den erinnerten Tupeln zu saugen und die Zeiger freizugeben. Wir können dies tun, da es keine Referenzen mehr aus den Indizes gibt.

Wenn die Tabelle während des ersten Zyklus nicht vollständig gelesen wurde, wird das Array gelöscht und alles wird an der Stelle wiederholt, an der wir angekommen sind.

Zusammenfassend:

  • Der Tisch wird immer zweimal gescannt.
  • Wenn beim Staubsaugen so viele Tupel gelöscht werden, dass sie nicht alle in den Speicher der Größe maintenance_work_mem passen, werden alle Indizes so oft wie nötig gescannt.

Bei großen Tabellen kann dies viel Zeit in Anspruch nehmen und eine erhebliche Systemauslastung verursachen. Natürlich werden Abfragen nicht gesperrt, aber eine zusätzliche Eingabe / Ausgabe ist definitiv unerwünscht.

Um den Vorgang zu beschleunigen, ist es sinnvoll, entweder öfter VACUUM aufzurufen (damit nicht zu viele Tupel jedes Mal abgesaugt werden) oder mehr Speicher zuzuweisen.

Ab Version 11 kann PostgreSQL Index-Scans überspringen, sofern kein zwingender Bedarf besteht. Dies muss den Eigentümern großer Tabellen, in denen nur Zeilen hinzugefügt (aber nicht geändert) werden, das Leben erleichtern.

Überwachung


Wie können wir herausfinden, dass VACUUM seine Arbeit nicht in einem Zyklus erledigen kann?

Wir haben bereits den ersten Weg gesehen: den Befehl VACUUM mit der Option VERBOSE aufzurufen. In diesem Fall werden Informationen zu den Phasen des Prozesses an die Konsole ausgegeben.

Zweitens ist ab Version 9.6 die Ansicht pg_stat_progress_vacuum verfügbar, die auch alle erforderlichen Informationen enthält.

(Der dritte Weg ist ebenfalls verfügbar: Die Informationen in das Nachrichtenprotokoll ausgeben, dies funktioniert jedoch nur für das automatische Vakuum, das beim nächsten Mal besprochen wird.)

Fügen wir einige Zeilen in die Tabelle ein, damit der Vakuumprozess ziemlich lange dauert, und aktualisieren wir alle Zeilen, damit VACUUM Aufgaben erledigt.

 => TRUNCATE vac; => INSERT INTO vac(s) SELECT 'A' FROM generate_series(1,500000); => UPDATE vac SET s = 'B'; 

Reduzieren Sie die für das Array von Bezeichnern zugewiesene Speichergröße:

 => ALTER SYSTEM SET maintenance_work_mem = '1MB'; => SELECT pg_reload_conf(); 

Starten wir VACUUM und greifen wir während des pg_stat_progress_vacuum mehrmals auf die Ansicht pg_stat_progress_vacuum :

 => VACUUM VERBOSE vac; 

 | => SELECT * FROM pg_stat_progress_vacuum \gx 
 | -[ RECORD 1 ]------+------------------ | pid | 6715 | datid | 41493 | datname | test | relid | 57383 | phase | vacuuming indexes | heap_blks_total | 16667 | heap_blks_scanned | 2908 | heap_blks_vacuumed | 0 | index_vacuum_count | 0 | max_dead_tuples | 174762 | num_dead_tuples | 174480 

 | => SELECT * FROM pg_stat_progress_vacuum \gx 
 | -[ RECORD 1 ]------+------------------ | pid | 6715 | datid | 41493 | datname | test | relid | 57383 | phase | vacuuming indexes | heap_blks_total | 16667 | heap_blks_scanned | 5816 | heap_blks_vacuumed | 2907 | index_vacuum_count | 1 | max_dead_tuples | 174762 | num_dead_tuples | 174480 

Hier können wir insbesondere sehen:

  • Der Name der aktuellen Phase - wir haben drei Hauptphasen besprochen, aber es gibt mehr davon im Allgemeinen.
  • Die Gesamtzahl der Tabellenseiten ( heap_blks_total ).
  • Die Anzahl der gescannten Seiten ( heap_blks_scanned ).
  • Die Anzahl der bereits heap_blks_vacuumed Seiten ( heap_blks_vacuumed ).
  • Die Anzahl der index_vacuum_count ( index_vacuum_count ).

Der allgemeine Fortschritt wird durch das Verhältnis von heap_blks_vacuumed zu heap_blks_total . Wir sollten jedoch berücksichtigen, dass sich dieser Wert aufgrund des Scannens der Indizes nicht reibungslos, sondern in großen Schritten ändert. Das Hauptaugenmerk sollte jedoch auf die Anzahl der Vakuumzyklen gelegt werden: Eine Zahl größer als 1 bedeutet, dass der zugewiesene Speicher nicht ausreicht, um das Saugen in einem Zyklus abzuschließen.

Die Ausgabe des zu diesem Zeitpunkt bereits abgeschlossenen Befehls VACUUM VERBOSE zeigt das allgemeine Bild:

 INFO: vacuuming "public.vac" 
 INFO: scanned index "vac_s" to remove 174480 row versions DETAIL: CPU: user: 0.50 s, system: 0.07 s, elapsed: 1.36 s INFO: "vac": removed 174480 row versions in 2908 pages DETAIL: CPU: user: 0.02 s, system: 0.02 s, elapsed: 0.13 s 
 INFO: scanned index "vac_s" to remove 174480 row versions DETAIL: CPU: user: 0.26 s, system: 0.07 s, elapsed: 0.81 s INFO: "vac": removed 174480 row versions in 2908 pages DETAIL: CPU: user: 0.01 s, system: 0.02 s, elapsed: 0.10 s 
 INFO: scanned index "vac_s" to remove 151040 row versions DETAIL: CPU: user: 0.13 s, system: 0.04 s, elapsed: 0.47 s INFO: "vac": removed 151040 row versions in 2518 pages DETAIL: CPU: user: 0.01 s, system: 0.02 s, elapsed: 0.08 s 
 INFO: index "vac_s" now contains 500000 row versions in 17821 pages DETAIL: 500000 index row versions were removed. 8778 index pages have been deleted, 0 are currently reusable. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. INFO: "vac": found 500000 removable, 500000 nonremovable row versions in 16667 out of 16667 pages DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 4011 There were 0 unused item pointers. 0 pages are entirely empty. CPU: user: 1.10 s, system: 0.37 s, elapsed: 3.71 s. VACUUM 

Wir können hier sehen, dass drei Zyklen über die Indizes durchgeführt wurden und in jedem Zyklus 174480 Zeiger auf tote Tupel abgesaugt wurden. Warum genau diese Nummer? Ein tid belegt 6 Bytes und 1024 * 1024/6 = 174762, die Zahl, die wir in pg_stat_progress_vacuum.max_dead_tuples . In der Realität kann etwas weniger verwendet werden: Dies stellt sicher, dass alle Zeiger auf tote Tupel in den Speicher passen, wenn eine nächste Seite gelesen wird.

Analyse


Die Analyse oder mit anderen Worten das Sammeln von Statistiken für den Abfrageplaner hat formal überhaupt nichts mit dem Staubsaugen zu tun. Trotzdem können wir die Analyse nicht nur mit dem Befehl ANALYZE durchführen, sondern auch Staubsaugen und Analyse in VACUUM ANALYZE kombinieren. Hier wird zuerst das Vakuum und dann die Analyse durchgeführt, so dass dies keine Gewinne ergibt.

Wie wir später sehen werden, werden Autovakuum und automatische Analyse in einem Prozess durchgeführt und auf ähnliche Weise gesteuert.

VAKUUM VOLL


Wie oben erwähnt, gibt Vakuum mehr Platz frei als In-Page-Vakuum, löst jedoch das Problem nicht vollständig.

Wenn die Größe einer Tabelle oder eines Index aus bestimmten Gründen stark zugenommen hat, gibt VACUUM Speicherplatz auf den vorhandenen Seiten frei: Dort treten "Löcher" auf, die dann zum Einfügen neuer Tupel verwendet werden. Die Anzahl der Seiten ändert sich jedoch nicht. Aus Sicht des Betriebssystems belegen die Dateien daher genau den gleichen Platz wie vor dem Vakuum. Und das ist nicht gut, weil:

  • Der vollständige Scan der Tabelle (oder des Index) wird langsamer.
  • Möglicherweise ist ein größerer Puffercache erforderlich (da die Seiten dort gespeichert werden und die Dichte der nützlichen Informationen abnimmt).
  • Im Indexbaum kann eine zusätzliche Ebene auftreten, die den Indexzugriff verlangsamt.
  • Die Dateien belegen zusätzlichen Speicherplatz auf der Festplatte und in Sicherungskopien.

(Die einzige Ausnahme sind vollständig gesaugte Seiten am Ende der Datei. Diese Seiten werden aus der Datei entfernt und an das Betriebssystem zurückgegeben.)

Wenn der Anteil nützlicher Informationen in den Dateien eine angemessene Grenze unterschreitet, kann der Administrator VACUUM FULL für die Tabelle ausführen. In diesem Fall werden die Tabelle und alle ihre Indizes von Grund auf neu erstellt und die Daten werden größtenteils kompakt gepackt (natürlich unter Berücksichtigung des fillfactor Parameters). Während der Neuerstellung erstellt PostgreSQL zuerst die Tabelle und dann jeden Index einzeln neu. Für jedes Objekt werden neue Dateien erstellt und alte Dateien am Ende der Neuerstellung entfernt. Wir sollten berücksichtigen, dass dabei zusätzlicher Speicherplatz benötigt wird.

Um dies zu veranschaulichen, fügen wir noch einmal eine bestimmte Anzahl von Zeilen in die Tabelle ein:

 => TRUNCATE vac; => INSERT INTO vac(s) SELECT 'A' FROM generate_series(1,500000); 

Wie können wir die Informationsdichte abschätzen? Dazu ist es bequem, eine spezielle Erweiterung zu verwenden:

 => CREATE EXTENSION pgstattuple; => SELECT * FROM pgstattuple('vac') \gx 
 -[ RECORD 1 ]------+--------- table_len | 68272128 tuple_count | 500000 tuple_len | 64500000 tuple_percent | 94.47 dead_tuple_count | 0 dead_tuple_len | 0 dead_tuple_percent | 0 free_space | 38776 free_percent | 0.06 

Die Funktion liest die gesamte Tabelle und zeigt Statistiken an: Welche Daten belegen wie viel Platz in den Dateien. Die Hauptinformation, die uns jetzt interessiert, ist das Feld tuple_percent : der Prozentsatz nützlicher Daten. Es ist weniger als 100 wegen des unvermeidlichen Informationsaufwands innerhalb einer Seite, aber es ist immer noch ziemlich hoch.

Für den Index werden unterschiedliche Informationen ausgegeben, aber das Feld avg_leaf_density hat dieselbe Bedeutung: den Prozentsatz nützlicher Informationen (in Blattseiten).

 => SELECT * FROM pgstatindex('vac_s') \gx 
 -[ RECORD 1 ]------+--------- version | 3 tree_level | 3 index_size | 72802304 root_block_no | 2722 internal_pages | 241 leaf_pages | 8645 empty_pages | 0 deleted_pages | 0 avg_leaf_density | 83.77 leaf_fragmentation | 64.25 

Und dies sind die Größen der Tabelle und der Indizes:

 => SELECT pg_size_pretty(pg_table_size('vac')) table_size, pg_size_pretty(pg_indexes_size('vac')) index_size; 
  table_size | index_size ------------+------------ 65 MB | 69 MB (1 row) 

Jetzt löschen wir 90% aller Zeilen. Wir wählen die zu löschenden Zeilen nach dem Zufallsprinzip aus, sodass mit hoher Wahrscheinlichkeit mindestens eine Zeile auf jeder Seite verbleibt:

 => DELETE FROM vac WHERE random() < 0.9; 
 DELETE 450189 

Welche Größe haben die Objekte nach VACUUM?

 => VACUUM vac; => SELECT pg_size_pretty(pg_table_size('vac')) table_size, pg_size_pretty(pg_indexes_size('vac')) index_size; 
  table_size | index_size ------------+------------ 65 MB | 69 MB (1 row) 

Wir können sehen, dass sich die Größe nicht geändert hat: VACUUM kann auf keinen Fall die Größe von Dateien reduzieren. Und das, obwohl die Informationsdichte um das Zehnfache abnahm:

 => SELECT vac.tuple_percent, vac_s.avg_leaf_density FROM pgstattuple('vac') vac, pgstatindex('vac_s') vac_s; 
  tuple_percent | avg_leaf_density ---------------+------------------ 9.41 | 9.73 (1 row) 

Lassen Sie uns nun überprüfen, was wir nach VACUUM FULL erhalten. Jetzt verwenden die Tabelle und die Indizes die folgenden Dateien:

 => SELECT pg_relation_filepath('vac'), pg_relation_filepath('vac_s'); 
  pg_relation_filepath | pg_relation_filepath ----------------------+---------------------- base/41493/57392 | base/41493/57393 (1 row) 

 => VACUUM FULL vac; => SELECT pg_relation_filepath('vac'), pg_relation_filepath('vac_s'); 
  pg_relation_filepath | pg_relation_filepath ----------------------+---------------------- base/41493/57404 | base/41493/57407 (1 row) 

Die Dateien werden jetzt durch neue ersetzt. Die Tabellen- und Indexgrößen nahmen erheblich ab, während sich die Informationsdichte entsprechend erhöhte:

 => SELECT pg_size_pretty(pg_table_size('vac')) table_size, pg_size_pretty(pg_indexes_size('vac')) index_size; 
  table_size | index_size ------------+------------ 6648 kB | 6480 kB (1 row) 
 => SELECT vac.tuple_percent, vac_s.avg_leaf_density FROM pgstattuple('vac') vac, pgstatindex('vac_s') vac_s; 
  tuple_percent | avg_leaf_density ---------------+------------------ 94.39 | 91.08 (1 row) 

Beachten Sie, dass die Informationsdichte im Index noch größer als die ursprüngliche ist. Es ist vorteilhafter, einen Index (B-Tree) aus den verfügbaren Daten neu zu erstellen, als die Daten zeilenweise in einen vorhandenen Index einzufügen.

Die Funktionen der pgstattuple- Erweiterung, die wir verwendet haben, haben die gesamte Tabelle gelesen. Dies ist jedoch unpraktisch, wenn die Tabelle groß ist. pgstattuple_approx verfügt die Erweiterung über die Funktion pgstattuple_approx , mit der die in der Sichtbarkeitskarte markierten Seiten pgstattuple_approx und ungefähre Zahlen pgstattuple_approx werden.

Eine weitere Möglichkeit, die jedoch noch ungenauer ist, ist die Verwendung des Systemkatalogs, um das Verhältnis der Datengröße zur Dateigröße grob abzuschätzen. Beispiele für solche Abfragen finden Sie im Wiki .

VACUUM FULL ist nicht für den regulären Gebrauch vorgesehen, da es die Arbeit mit der Tabelle (einschließlich Abfrage) für die gesamte Dauer des Prozesses blockiert. Es ist klar, dass dies für ein stark genutztes System nicht akzeptabel erscheint. Sperren werden separat behandelt, und jetzt erwähnen wir nur die Erweiterung pg_repack , die die Tabelle am Ende der Arbeit nur für einen kurzen Zeitraum sperrt.

Ähnliche Befehle


Es gibt einige Befehle, die auch Tabellen und Indizes vollständig neu erstellen und daher VACUUM FULL ähneln. Alle blockieren die Arbeit mit der Tabelle vollständig, alle entfernen alte Datendateien und erstellen neue.

Der Befehl CLUSTER ähnelt in allen Punkten VACUUM FULL, ordnet jedoch Tupel physisch gemäß einem der verfügbaren Indizes an. Dies ermöglicht es dem Planer, den Indexzugriff in einigen Fällen effizienter zu nutzen. Wir sollten jedoch bedenken, dass die Clusterbildung nicht aufrechterhalten wird: Die physikalische Reihenfolge der Tupel wird bei nachfolgenden Änderungen der Tabelle unterbrochen.

Der Befehl REINDEX erstellt einen separaten Index für die Tabelle neu. VACUUM FULL und CLUSTER verwenden diesen Befehl tatsächlich, um Indizes neu zu erstellen.

Die Logik des Befehls TRUNCATE ähnelt der von DELETE - es werden alle Tabellenzeilen gelöscht. Aber DELETE markiert, wie bereits erwähnt, nur Tupel als gelöscht, und dies erfordert ein weiteres Staubsaugen. Und TRUNCATE erstellt stattdessen nur eine neue, saubere Datei. In der Regel funktioniert dies schneller, aber wir sollten bedenken, dass TRUNCATE jede Arbeit mit der Tabelle bis zum Ende der Transaktion blockiert.

Lesen Sie weiter .

Source: https://habr.com/ru/post/de484106/


All Articles