Ich spiele derzeit damit herum, Probleme in einer DB zu erkennen.
Der hochgeschätzte Kollege @laurenz hatte mal in einem Vortrag die Idee vorgestellt, den Datentyp einer Tabelle im Katalog zu ändern. (“Katalogtabellen ändern ist immer ein guter Weg, die Datenbank zu zerstören.”)
Also los:
-- Ausgangssituation
\d test2
Tabelle »public.test2«
Spalte | Typ | Sortierfolge | NULL erlaubt? | Vorgabewert
--------+---------+--------------+---------------+-----------------------------------
id | integer | | not null | nextval('test2_id_seq'::regclass)
wert | text | | |
Indexe:
"test2_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
TABLE "test2_fk" CONSTRAINT "test2_fk_test2_fkey" FOREIGN KEY (test2) REFERENCES test2(id)
select min(id), max(id) from test2;
min | max
-----+---------
101 | 1000000
(1 Zeile)
-- Bad things happen
UPDATE pg_attribute
SET atttypid = 'smallint'::regtype
WHERE attrelid = 'test2'::regclass AND attname = 'id';
\d test2
Tabelle »public.test2«
Spalte | Typ | Sortierfolge | NULL erlaubt? | Vorgabewert
--------+----------+--------------+---------------+-----------------------------------
id | smallint | | not null | nextval('test2_id_seq'::regclass)
wert | text | | |
Indexe:
"test2_pkey" PRIMARY KEY, btree (id int4_ops)
Fremdschlüsselverweise von:
TABLE "test2_fk" CONSTRAINT "test2_fk_test2_fkey" FOREIGN KEY (test2) REFERENCES test2(id)
andreas=# select min(id), max(id) from test2;
min | max
--------+-------
-32768 | 32767
(1 Zeile)
andreas=# select min(test2), max(test2) from test2_fk;
min | max
-----+---------
101 | 1000017
(1 Zeile)
select id, count(*) from test2 group by 1 order by 2 desc limit 10;
id | count
-------+-------
15805 | 16
8869 | 16
1552 | 16
1373 | 16
161 | 16
1595 | 16
9038 | 16
5697 | 16
10792 | 16
8660 | 16
(10 Zeilen)
SELECT bt_index_check('test2_pkey'::regclass, true, true);
bt_index_check
----------------
(1 Zeile)
select version();
version
----------------------------------------------------------------------------------------------------------------------------------
PostgreSQL 17.7 on x86_64-pc-linux-gnu, compiled by x86_64-pc-linux-gnu-gcc (Gentoo 15.2.1_p20251122 p3) 15.2.1 20251122, 64-bit
(1 Zeile)
Ich sehe derzeit keine Möglichkeit soetwas zu entdecken. Die armen Waisen in test2_fk werden nie gefunden
REINDEX läuft durch
pg_amcheck läuft durch (s. auch oben) – hier hatte ich ja auf checkunique gesetzt
pg_dump läuft durch
pg_restore fällt hin, beim Anlegen des PK auf test2
Das ist ein Thema, das ich immer wieder beobachte: Leute sind überrascht, weil Datenkorruption nicht notwendigerweise sofort auffällt. Ich begegne regelmäßig Menschen, die Befremden darüber äußern, daß der Server ohne lautes Schreien startet, wenn die Datenbank korrupt ist — wobei sich dieselben Leute wahrscheinlich auch laut beschweren würden, wenn der Start einer 10TB-Datenbank mehrere Stunden dauert, weil alle Blöcke überprüft werden.
Im konkreten Fall werden die bestehenden Zeilen wohl deshalb ohne Fehler gelesen, weil pg_attribute.attlen immer noch 4 ist.
Sicher könnte man in PostgreSQL noch mehr Tests auf Konsistenz machen, etwa ob der Datentyp und seine Größe in pg_attribute zusammenpassen. Aber solche Tests kosten Zeit.
Wenn man PostgreSQL mit --enable-cassert konfiguriert und kompiliert, werden tatsächlich wesentlich mehr Invarianten überprüft (“sanity checks”). Allerdings läuft die Datenbank dann merklich langsamer, und für Produktivsysteme ist das nicht ratsam.
Es ist schwierig, die gute Balance zwischen Paranoia und Performance zu finden.
Sowas erwarte ich doch gar nicht. Was ich aber schon erstaunlich fand: pg_amcheck (mit isunique) findet kein Problem und eine Reindex auf den kaputten PK (Duplikate ohne Ende) sieht keine Schwierigkeiten.
Das pg_dump durch läuft ist ja durchaus verständlich.
Mir geht es ja darum, was ich tun kann, um sowas zu finden.
Ich finde das geziemend. Sobald man in der Tabelle ein INSERT oder UPDATE ausführt, knallt es. Und verify_heapam() findet das Problem auch. Besser geht’s kaum.
Diese konkrete Datenkorruption is auch sehr künstlich.