Wie viel Platz braucht NULL?
Beim letzten Beratungseinsatz beim Kunden, kam dieser freudestrahlend auf mich zu mit der Bemerkung: Er habe meinen Rat befolgt und sämtiche Primary Key Spalten von BIGINT (8 byte) auf INT (4 byte) geändert und das habe viel gebracht! Seine MySQL 8.4er Datenbank sei jetzt um 750 Gbyte kleiner geworden (von 5.5 Tbyte). Schön!
Und ja, ich weiss, dass wiederspricht den Empfehlungen einiger meiner PostgreSQL Kollegen (hier und hier). In der MySQL-Welt wird eher auf solche Dinge Wert gelegt (Quelle):
Use the most efficient (smallest) data types possible. MySQL has many specialized types that save disk space and memory. For example, use the smaller integer types if possible to get smaller tables
Zudem funktioniert InnoDB ein klein wening anders (Index Clusterd Table und Primary Key in allen Secondary Keys) als PostgreSQL (Heap Table, Indices mit Row Pointer (ctid)).
Aber das ist eigentlich nicht das Thema. Sofort im Anschluss kam er nämlich mit der Frage, ob das AusNULLen von Spalten vom Typ DOUBLE (8 byte, in PostgreSQL-Sprech DOUBLE PRECISION) auch Platzeinsparungen brächte oder ob er die Spalten lieber gleich droppen solle. Meine erste reflexartige Antwort bei DOUBLE war: NULL bringt was, gefolgt von OPTIMIZE TABLE (in PostgreSQL-Sprech VACUUM FULL). Der zweite Gedanke war aber, DOUBLE ist ein Datentyp fixer länger, gilt das mit NULL da auch oder nur bei Datentypen mit variabler Länge? Vorsicht ist die Mutter der Porzelankiste! Liebe zuerst mal das Manual konsultieren…
Und dort steht (Quelle):
Declare columns to be NOT NULL if possible. It makes SQL operations faster, by enabling better use of indexes and eliminating overhead for testing whether each value is NULL. You also save some storage space, one bit per column. If you really need NULL values in your tables, use them. Just avoid the default setting that allows NULL values in every column.
und (Quelle):
The variable-length part of the record header contains a bit vector for indicating NULL columns. … Columns that are NULL do not occupy space other than the bit in this vector. The variable-length part of the header also contains the lengths of variable-length columns. Each length takes one or two bytes, depending on the maximum length of the column. If all columns in the index are NOT NULL and have a fixed length, the record header has no variable-length part.
Versuch mit MariaDB/MySQL
Versuchsaufbau
Irgendwie ist mir das etwas zu komplizert beschrieben. Eine kleine Skizze würde vielleicht helfen? Also machen wir einen Versuch:
SQL> -- DROP TABLE IF EXISTS tracking;
SQL> CREATE TABLE tracking (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT
, d0 DOUBLE, d1 DOUBLE, d2 DOUBLE, d3 DOUBLE, d4 DOUBLE
, d5 DOUBLE, d6 DOUBLE, d7 DOUBLE, d8 DOUBLE, d9 DOUBLE
);
SQL> INSERT INTO tracking SELECT NULL, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0;
SQL> INSERT INTO tracking SELECT NULL, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 FROM tracking;
... bis 16 M rows
Die Tabelle wird sowohl bei MariaDB als auch bei MySQL bei 16 M Rows ca. 1.8 Gbyte gross. Da dieses Informationen im INFORMATION_SCHEMA nur sehr unpräzise angegeben werden schauen wir auf dem Filesystem nach:
MariaDB 11.8:
SQL> system ls -l tracking.ibd
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:28 tracking.frm
-rw-rw---- 1 mysql mysql 1933574144 Feb 7 10:32 tracking.ibd
MySQL 8.4:
SQL> system ls -l tracking.ibd
-rw-r----- 1 mysql mysql 1929379840 Feb 7 10:33 tracking.ibd
Defragementieren der Tabelle
Dann “defragmentieren” wir die Tabelle mit dem OPTIMIZE TABLE Befehl:
SQL> OPTIMIZE TABLE tracking;
+---------------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+---------------+----------+----------+-------------------------------------------------------------------+
| test.tracking | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| test.tracking | optimize | status | OK |
+---------------+----------+----------+-------------------------------------------------------------------+
Achtung: Die Tabelle wir einmal umkopiert! Es braucht also kurzzeitig die doppelte Menge an Diskplatz! Dies können wird schön beobachten während der OPTIMIZE TABLE Befehls läuft:
MariaDB:
$ watch -d -n 1 'ls -l trac* \#*'
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:39 '#sql-alter-d57-8c.frm'
-rw-rw---- 1 mysql mysql 968884224 Feb 7 10:39 '#sql-alter-d57-8c.ibd'
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:28 tracking.frm
-rw-rw---- 1 mysql mysql 1933574144 Feb 7 10:32 tracking.ibd
MySQL:
$ watch -d -n 1 'ls -l trac* \#*'
-rw-r----- 1 mysql mysql 369098752 Feb 7 10:40 #sql-ib1594-4164062678.ibd
-rw-r----- 1 mysql mysql 1929379840 Feb 7 10:33 tracking.ibd
Das Resultat ist dann aber erstaunlich! Bei MariaDB ist die Tabelle in etwas gleich gross geblieben:
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:39 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 10:39 tracking.ibd
Bei MySQL hingegen ist die Tabelle nach dem “defragmentieren” sogar angewachsen und zwar um ca. 14%:
-rw-r----- 1 mysql mysql 2197815296 Feb 7 10:41 tracking.ibd
Wenn wir den OPTIMIZE TABLE Befehl nochmal ausführen bleibt die Grösse sowohl bei MariaDB als auch MySQL konstant:
MariaDB:
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:46 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 10:48 tracking.ibd
MySQL:
-rw-r----- 1 mysql mysql 2197815296 Feb 7 10:48 tracking.ibd
Versuch 1: AusNULLen
Jetzt NULLen wir die Werte aus:
SQL> UPDATE tracking
SET d0 = NULL, d1 = NULL, d2 = NULL, d3 = NULL, d4 = NULL
, d5 = NULL, d6 = NULL, d7 = NULL, d8 = NULL, d9 = NULL
;
Nach diesem Schritt sind die Grössen der Dateien sogar noch etwas gewachsen:
MariaDB (+1.3%):
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:49 tracking.frm
-rw-rw---- 1 mysql mysql 1937768448 Feb 7 11:04 tracking.ibd
MySQL (+0.2%):
-rw-r----- 1 mysql mysql 2202009600 Feb 7 11:04 tracking.ibd
Anschliessend defragemntieren wir die Tabelle erneut mit dem OPTIMIZE TABLE Befehl. Die Tabellen schrumpfen wie erwartet.
MariaDB (auf 23%):
-rw-rw---- 1 mysql mysql 1206 Feb 7 11:09 tracking.frm
-rw-rw---- 1 mysql mysql 448790528 Feb 7 11:10 tracking.ibd
MySQL (auf 24%):
-rw-r----- 1 mysql mysql 520093696 Feb 7 11:10 tracking.ibd
Ein erneutes OPTIMIZE TABLE bringt anschliessend KEINE Veränderung mehr in der Dateigrösse…
Versuch 2: Löschen der Spalten
Jetzt probieren wir das Ganze nochmal mit dem DROP COLUMN Befehl. Die Ausganglage ist wieder die selbe wie oben beschrieben:
MariaDB:
-rw-rw---- 1 mysql mysql 1206 Feb 7 11:15 tracking.frm
-rw-rw---- 1 mysql mysql 1933574144 Feb 7 11:18 tracking.ibd
MySQL:
-rw-r----- 1 mysql mysql 1929379840 Feb 7 11:19 tracking.ibd
Nach dem OPTIMIZE TABLE Befehl sehen die Werte ähnlich aus wie im ersten Versuch:
MariaDB:
-rw-rw---- 1 mysql mysql 1206 Feb 7 11:20 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 11:21 tracking.ibd
MySQL:
-rw-r----- 1 mysql mysql 2197815296 Feb 7 11:21 tracking.ibd
Ein erneutes OPTIMIZE TABLE bring ebenfalls keine weiteren Veränderungen mehr, wie oben:
MariaDB:
-rw-rw---- 1 mysql mysql 1206 Feb 7 11:22 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 11:23 tracking.ibd
MySQL:
-rw-r----- 1 mysql mysql 2197815296 Feb 7 11:24 tracking.ibd
Und jetzt der eigentliche zweite Versuch mit Löschen der Spalten:
SQL> ALTER TABLE tracking
DROP COLUMN d0, DROP COLUMN d1, DROP COLUMN d2, DROP COLUMN d3, DROP COLUMN d4
, DROP COLUMN d5, DROP COLUMN d6, DROP COLUMN d7, DROP COLUMN d8, DROP COLUMN d9
;
Als erstes stellen wir fest, dass der Befehl INSTANTANEOUS ist, als keinerlei Änderung an den Daten vornimmt sondern nur Änderung an den Metadaten. Das ist auf der einen Seite gut, da der Einfluss auf die Applikation dadurch minimal gehalten wird.. Auf der anderen Seite bedeutet das ja auch: Keine Platzersparnis.
Also rücken wir dem Ganzen wieder mit dem OPTIMIZE TABLE Befehl zu Leibe:
MariaDB (auf 93%):
-rw-rw---- 1 mysql mysql 925 Feb 7 11:28 tracking.frm
-rw-rw---- 1 mysql mysql 415236096 Feb 7 11:29 tracking.ibd
MySQL (auf 92%):
-rw-r----- 1 mysql mysql 478150656 Feb 7 11:28 tracking.ibd
Fazit
Sowohl das ausNULLen der Spalten als auch das Löschen der Spalten bringen signifikante Platzersparnis. Wobei das Löschen der Spalten nochmal ca. 7% mehr bringt als das ausNULLen. Wenn es applikatorisch möglich ist, sollte man daher nicht mehr benötigte Spalen löschen, wenn nicht möglich, diese zumindest ausNULLen.
Versuch mit PostgreSQL
Und jetzt schauen wir uns das Ganze noch bei PostgreSQL 19devel an.
Versuchsaufbau
Der Versuchsaufbau erfolgt analog zu MariaDB/MySQL:
postgres=# -- DROP TABLE IF EXISTS tracking;
postgres=# CREATE TABLE tracking (
id SERIAL PRIMARY KEY
, d0 DOUBLE PRECISION, d1 DOUBLE PRECISION, d2 DOUBLE PRECISION, d3 DOUBLE PRECISION, d4 DOUBLE PRECISION
, d5 DOUBLE PRECISION, d6 DOUBLE PRECISION, d7 DOUBLE PRECISION, d8 DOUBLE PRECISION, d9 DOUBLE PRECISION
);
postgres=# \timing
postgres=# INSERT INTO tracking (d0, d1, d2, d3, d4, d5, d6, d7, d8, d9)
SELECT 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0;
postgres=# INSERT INTO tracking (d0, d1, d2, d3, d4, d5, d6, d7, d8, d9)
SELECT 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 FROM tracking;
... bis 16 M rows
Zuerst wollen wir mal wissen, wie gross die Tabelle eigentlich geworden ist. PostgreSQL scheint diese Informationen sehr genau zu kennen:
postgres=# SELECT pg_relation_size('tracking') AS tab_siz
, pg_size_pretty(pg_relation_size('tracking')) AS tab_siz_prtty
, pg_indexes_size('tracking') AS idx_siz
, pg_size_pretty(pg_indexes_size('tracking')) AS idx_siz_prtty
, pg_relation_size('tracking') + pg_indexes_size('tracking') AS tab_and_idx_siz
, pg_size_pretty(pg_relation_size('tracking') + pg_indexes_size('tracking')) AS tab_and_idx_siz_prtty
, pg_total_relation_size('tracking') AS tot_rel_siz
, pg_size_pretty(pg_total_relation_size('tracking')) AS tot_rel_siz_prtty
;
tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
------------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
1963417600 | 1872 MB | 376856576 | 359 MB | 2340274176 | 2232 MB | 2340798464 | 2232 MB
Dann wollen wir wissen, wo im Filesystem diese Dateien zu finden sind:
postgres=# SELECT oid AS db_oid FROM pg_database WHERE datname = current_database();
db_oid
--------
5
postgres=# SELECT oid AS table_oid, relname, relnamespace, relfilenode
FROM pg_class WHERE relname = 'tracking';
table_oid | relname | relnamespace | relfilenode
-----------+----------+--------------+-------------
40965 | tracking | 2200 | 40965
postgres=# SELECT i.indexrelid::regclass as index_name, i.indexrelid as index_oid
FROM pg_index i
JOIN pg_class c ON i.indrelid = c.oid
WHERE c.relname = 'tracking';
index_name | index_oid
---------------+-----------
tracking_pkey | 40970
postgres=# SELECT pg_relation_filepath('tracking');
pg_relation_filepath
----------------------
base/5/40965
Tabellen- und Indexgrösse im Filesystem:
$ ls -ltr 40965* 40970*
-rw------- 1 mysql mysql 40960 Feb 7 18:33 40965_vm
-rw------- 1 mysql mysql 499712 Feb 7 18:33 40965_fsm
-rw------- 1 mysql mysql 889675776 Feb 7 18:34 40965.1
-rw------- 1 mysql mysql 1073741824 Feb 7 18:34 40965
-rw------- 1 mysql mysql 376856576 Feb 7 18:35 40970
*_fsmbedeutet “free space map”*_vmbedeutet “visibility map”*.1bedeutet 2. Segment des Objekts (Tabelle oder Index)
PostgreSQL scheint per default mit Segmenten von 1 Gbyte Grösse zu arbeiten und im Unterschied zu MariaDB/MySQL (INFORMATION_SCHEMA) sehr genau zu wissen, wie gross seine Dateien sind. Und die Diskrepanz von oben (zwischen tot_rel_siz und tab_and_idx_siz) lässt sich durch die fsm und die vm-Dateien erklären.
Das PostgreSQL Equivalent zum MariaDB/MySQL OPTIMIZE TABLE ist der Befehl VACUUM FULL:
postgres=# VACUUM FULL tracking;
$ ls -ltr
-rw------- 1 mysql mysql 40960 Feb 7 18:39 40965_vm
-rw------- 1 mysql mysql 499712 Feb 7 18:39 40965_fsm
-rw------- 1 mysql mysql 1073741824 Feb 7 18:39 40965
-rw------- 1 mysql mysql 889675776 Feb 7 18:39 40965.1
-rw------- 1 mysql mysql 1073741824 Feb 7 18:39 40972
-rw------- 1 mysql mysql 889675776 Feb 7 18:39 40972.1
-rw------- 1 mysql mysql 0 Feb 7 18:39 40975
...
-rw------- 1 mysql mysql 49152 Feb 7 18:39 2704
-rw------- 1 mysql mysql 32768 Feb 7 18:39 2703
-rw------- 1 mysql mysql 32768 Feb 7 18:39 2696
-rw------- 1 mysql mysql 65536 Feb 7 18:39 2674
-rw------- 1 mysql mysql 81920 Feb 7 18:39 2673
-rw------- 1 mysql mysql 98304 Feb 7 18:39 2659
-rw------- 1 mysql mysql 139264 Feb 7 18:39 2658
-rw------- 1 mysql mysql 24576 Feb 7 18:39 2619_fsm
-rw------- 1 mysql mysql 163840 Feb 7 18:39 2619
-rw------- 1 mysql mysql 106496 Feb 7 18:39 2608
-rw------- 1 mysql mysql 491520 Feb 7 18:39 1249
-rw------- 1 mysql mysql 122880 Feb 7 18:39 1247
-rw------- 1 mysql mysql 32768 Feb 7 18:39 2662
-rw------- 1 mysql mysql 114688 Feb 7 18:39 1259
-rw------- 1 mysql mysql 16384 Feb 7 18:39 3455
-rw------- 1 mysql mysql 49152 Feb 7 18:39 2663
-rw------- 1 mysql mysql 1073741824 Feb 7 18:39 40972
-rw------- 1 mysql mysql 889675776 Feb 7 18:39 40972.1
-rw------- 1 mysql mysql 376864768 Feb 7 18:39 40975
-rw------- 1 mysql mysql 0 Feb 7 18:39 40965
-rw------- 1 mysql mysql 0 Feb 7 18:39 40970
Das erste, was man feststellt, ist, dass PostgreSQL ganz viele Dateien anlangt und die “free space map” Datei ist verschwunden. Die Tabellen-Segmente sind dafür, im Unterschied zu MariaDB/MySQL gleich gross geblieben. Zudem sieht man, dass die alte Tabelle “verschwunden” (40965, 40970) und eine neue entstanden (40972 und 40975) ist. Der VACUUM FULL Befehl bei PostgreSQL erstellt also ebenfalls, wie bei MariaDB/MySQL eine Kopie der Daten.
postgres=# SELECT pg_relation_size('tracking') AS tab_siz
, pg_size_pretty(pg_relation_size('tracking')) AS tab_siz_prtty
, pg_indexes_size('tracking') AS idx_siz
, pg_size_pretty(pg_indexes_size('tracking')) AS idx_siz_prtty
, pg_relation_size('tracking') + pg_indexes_size('tracking') AS tab_and_idx_siz
, pg_size_pretty(pg_relation_size('tracking') + pg_indexes_size('tracking')) AS tab_and_idx_siz_prtty
, pg_total_relation_size('tracking') AS tot_rel_siz
, pg_size_pretty(pg_total_relation_size('tracking')) AS tot_rel_siz_prtty
;
tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
------------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
1963417600 | 1872 MB | 376864768 | 359 MB | 2340282368 | 2232 MB | 2340282368 | 2232 MB
Um zu verstehen, welche Dateien/Objekte denn sonst noch so alles angelangt wurden, hilft folgendes Query:
postgres=# SELECT c.oid, c.relname, ns.nspname
FROM pg_class AS c
JOIN pg_namespace AS ns ON ns.oid = c.relnamespace
WHERE c.oid IN (2704, 2703, 2696, 2674, 2673, 2659, 2658, 2619, 2608, 1249, 1247, 40972, 2662, 1259, 3455, 2663, 40975, 40965, 40970)
;
oid | relname | nspname
-------+-----------------------------------+------------
40965 | tracking | public
40970 | tracking_pkey | public
2619 | pg_statistic | pg_catalog
1247 | pg_type | pg_catalog
2703 | pg_type_oid_index | pg_catalog
2704 | pg_type_typname_nsp_index | pg_catalog
2658 | pg_attribute_relid_attnam_index | pg_catalog
2659 | pg_attribute_relid_attnum_index | pg_catalog
2662 | pg_class_oid_index | pg_catalog
2663 | pg_class_relname_nsp_index | pg_catalog
3455 | pg_class_tblspc_relfilenode_index | pg_catalog
2696 | pg_statistic_relid_att_inh_index | pg_catalog
2673 | pg_depend_depender_index | pg_catalog
2674 | pg_depend_reference_index | pg_catalog
1249 | pg_attribute | pg_catalog
1259 | pg_class | pg_catalog
2608 | pg_depend | pg_catalog
postgres=# SELECT i.indexrelid::regclass as index_name, i.indexrelid as index_oid, ns.nspname
FROM pg_index i
JOIN pg_class c ON i.indrelid = c.oid
JOIN pg_namespace AS ns ON ns.oid = c.relnamespace
WHERE c.oid IN (2704, 2703, 2696, 2674, 2673, 2659, 2658, 2619, 2608, 1249, 1247, 40972, 2662, 1259, 3455, 2663, 40975, 40965, 40970)
;
index_name | index_oid | nspname
-----------------------------------+-----------+------------
pg_type_typname_nsp_index | 2704 | pg_catalog
pg_attribute_relid_attnam_index | 2658 | pg_catalog
tracking_pkey | 40970 | public
pg_class_relname_nsp_index | 2663 | pg_catalog
pg_class_tblspc_relfilenode_index | 3455 | pg_catalog
pg_type_oid_index | 2703 | pg_catalog
pg_attribute_relid_attnum_index | 2659 | pg_catalog
pg_statistic_relid_att_inh_index | 2696 | pg_catalog
pg_depend_depender_index | 2673 | pg_catalog
pg_depend_reference_index | 2674 | pg_catalog
pg_class_oid_index | 2662 | pg_catalog
Versuch 1: AusNULLen
Dann NULLen wir die Spalten bei PostgreSQL ebenfalls aus. Die Sicht aufs Datei-System sparen wir uns ab hier, da PostgreSQL die Dateigrössen ja genau zu kennen scheint, wie wir oben gesehen haben:
postgres=# UPDATE tracking
SET d0 = NULL, d1 = NULL, d2 = NULL, d3 = NULL, d4 = NULL
, d5 = NULL, d6 = NULL, d7 = NULL, d8 = NULL, d9 = NULL
;
postgres=# SELECT pg_relation_size('tracking') AS tab_siz
, pg_size_pretty(pg_relation_size('tracking')) AS tab_siz_prtty
, pg_indexes_size('tracking') AS idx_siz
, pg_size_pretty(pg_indexes_size('tracking')) AS idx_siz_prtty
, pg_relation_size('tracking') + pg_indexes_size('tracking') AS tab_and_idx_siz
, pg_size_pretty(pg_relation_size('tracking') + pg_indexes_size('tracking')) AS tab_and_idx_siz_prtty
, pg_total_relation_size('tracking') AS tot_rel_siz
, pg_size_pretty(pg_total_relation_size('tracking')) AS tot_rel_siz_prtty
;
tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
------------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
2695716864 | 2571 MB | 753696768 | 719 MB | 3449413632 | 3290 MB | 3450101760 | 3290 MB
Hier sehen wir, dass die Tabellen-Segmente massiv anwachsen (+37%), was in der PostgreSQL Terminologie als “bloat” bezeichnet wird. Die MVCC Implementierung von PostgreSQL speichert sowohl die alte auch nie neue Version der Row “in-place” also direkt in der Tabelle im Gegensatz zu MariaDB/MySQL welche die alte Version im UNDO Space und die neue Row “in-place” speichert. Auch die Index-Datei vergrössert sich signifikant (+100%). Dazu müssen wir noch weiter forschen, warum das so ist. Zudem wird wieder eine “free space map” erstellt (Differenz zwischen tot_rel_siz und tab_and_idx_siz).
Ein anschliessendes VACUUM FULL verkleinert die Tabelle (auf 28%) und den Index (auf 50%) wieder im Bezug auf die vorherige Grösse:
postgres=# VACUUM FULL tracking;
postgres=# SELECT pg_relation_size('tracking') AS tab_siz
, pg_size_pretty(pg_relation_size('tracking')) AS tab_siz_prtty
, pg_indexes_size('tracking') AS idx_siz
, pg_size_pretty(pg_indexes_size('tracking')) AS idx_siz_prtty
, pg_relation_size('tracking') + pg_indexes_size('tracking') AS tab_and_idx_siz
, pg_size_pretty(pg_relation_size('tracking') + pg_indexes_size('tracking')) AS tab_and_idx_siz_prtty
, pg_total_relation_size('tracking') AS tot_rel_siz
, pg_size_pretty(pg_total_relation_size('tracking')) AS tot_rel_siz_prtty
;
tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
-----------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
742916096 | 709 MB | 376864768 | 359 MB | 1119780864 | 1068 MB | 1119780864 | 1068 MB
und auch in Bezug zur ursprünglichen Grösse wird die Tabelle (auf 38%) und der Index (auf 100%) wieder kleiner. Warum der Index gleich gross geblieben ist und nur die Tabelle geschrumpft ist, muss noch recherchiert werden…
Versuch 2: Löschen der Spalten
Anschliessend werden die Spalten noch mit DROP COLUMN gelöscht.
postgres=# ALTER TABLE tracking
DROP COLUMN d0, DROP COLUMN d1, DROP COLUMN d2, DROP COLUMN d3, DROP COLUMN d4
, DROP COLUMN d5, DROP COLUMN d6, DROP COLUMN d7, DROP COLUMN d8, DROP COLUMN d9
;
Da die Rückmeldung sofort kam, kann davon ausgegangen werden, dass diese Operation ebenfalls instant erfolgt. Leider habe ich auf die Schnelle nichts in der PostgreSQL Dokumentation dazu gefunden.
An der Grösse hat sich nichts signifikant geändert, was bei einer Instant-Operation ja eigentlich auch zu erwarten ist. Dass sich jedoch nach dem VACUUM FULL Befehl die Grösse nicht mehr geändert hat, hat doch ein wenig erstaunt:
tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
-----------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
742916096 | 709 MB | 376864768 | 359 MB | 1119780864 | 1068 MB | 1120010240 | 1068 MB
postgres=# VACUUM FULL tracking;
tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
-----------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
742916096 | 709 MB | 376864768 | 359 MB | 1119780864 | 1068 MB | 1119780864 | 1068 MB
Bemerkungen
Locking bei PostgreSQL funktioniert wie folgt:
VACUUMNebenläufige DML Befehle sind möglich ähnlich wie beim MariaDB/MySQLOPTIMIZE TABLEBefehl. Das Resultat ist aber nicht ganz das Selbe.VACUUM FULLverursacht einenACCESS EXCLUSIVELock. Ähnlich wie bei MariaDB/MySQL 5.5 und älter beimOPTIMIZE TABLEBefehl. DML undSELECTBefehle sind NICHT zulässig.
Quellen
- How to Get Sizes of Database Objects in PostgreSQL
- Database File Layout
- System Administration Functions
- CLUSTER
- VACUUM
- Explicit Locking
Zusatzversuche
- Statt
0.0wurdeNULLin die Spaltend0-d9abgefüllt. Die Tabelle blieb klein (tot_rel_siz_prtty = 1068 MB). Es lohnt sich also auch bei PostgreSQLNULLstatt Dummy-Werte zu speichern. - Die Spalten
d0-d9wurden mitDOUBLE PRECISION NOT NULLangelegt und die Werte0.0abgefüllt. Keinen Effekt: Die Tabelle blieb gross (tot_rel_siz_prtty = 2232 MB).

