Inkonsistenzen in Datenbankindexen können aus verschiedenen Gründen auftreten, darunter Softwarefehler, Hardwareprobleme oder zugrunde liegenden Verhaltensänderungen wie Sortierreihenfolge-Änderungen.
Die PostgreSQL-Community hat Tools entwickelt, um solche Probleme zu identifizieren und zu beheben. Dazu gehören Tools wie amcheck, die von der PostgreSQL-Community empfohlen werden, um Konsistenzprobleme zu identifizieren, einschließlich Problemen, die in früheren Versionen von PostgreSQL 14 aufgetreten sind.
Wir haben dieses Playbook als Referenz für Cloud SQL for PostgreSQL-Nutzer geschrieben, die solche Probleme haben. Wir hoffen, dass diese Seite Informationen enthält, die auch anderen PostgreSQL-Nutzern helfen, inkonsistente B-Baum-Indexe zu identifizieren und zu beheben. Unser Ziel ist es, dieses Dokument kontinuierlich als Ressource für die gesamte Open-Source-Community zu verbessern. Wenn Sie uns Feedback geben möchten, verwenden Sie bitte die Schaltfläche „Feedback senden” oben und unten auf dieser Seite.
Zum Beheben von Inkonsistenzen eines Index sind folgende Schritte erforderlich:
-
Bevor Sie mit der Neuindexierung beginnen, sollten Sie Ihre Datenbank sichern, die richtigen Berechtigungen festlegen, Ihre
psql
-Clientversion prüfen und dieamcheck
-Erweiterung herunterladen. Auf inkonsistente B-Baum-Indexe prüfen.
Um die Indexe zu identifizieren, für die Sie Inkonsistenzen beheben müssen, müssen Sie alle B-Baum-Indexe mit Inkonsistenzen und alle eindeutigen und Primärschlüsselverletzungen identifizieren.
Inkonsistenzen des Index korrigieren.
Die Neuindexierung eines Index behebt alle Inkonsistenzen. Möglicherweise müssen Sie die Speichereinstellungen der Instanz anpassen, um die Leistung zu verbessern.
Neuindexierungsvorgänge beobachten.
Wir empfehlen Ihnen, den Fortschritt der Neuindexierung zu überwachen, um sicherzustellen, dass der Vorgang fortgesetzt und nicht blockiert wird.
Prüfen Sie, ob die Indexe konsistent sind.
Nachdem Sie den Index erfolgreich neu indexiert haben, sollten Sie prüfen, ob der Index Inkonsistenzen enthält.
Hinweis
Datenbank sichern
Um sicherzustellen, dass während der Neuindexierung keine Daten verloren gehen, empfehlen wir Ihnen, Ihre Datenbank zu sichern. Weitere Informationen finden Sie unter On-Demand-Sicherung erstellen.
Berechtigung cloudsqlsuperuser
festlegen
Zum Ausführen der Schritte auf dieser Seite benötigen Sie Berechtigungen des Typs cloudsqlsuperuser
. Weitere Informationen finden Sie unter session_replication_role.
Achten Sie darauf, dass die psql
-Clientversion 9.6 oder höher ist
Für die Schritte auf dieser Seite müssen Sie darauf achten, dass Ihre psql
-Clientversion 9.6 oder höher ist. Führen Sie den Befehl psql --version
aus, um Ihre aktuelle psql
-Clientversion zu prüfen.
Amcheck-Erweiterung installieren
Um auf Indexinkonsistenzen zu prüfen, müssen Sie die amcheck
-Erweiterung aktivieren.
PostgreSQL 9.6
Führen Sie die folgende Anweisung aus, um amcheck
für PostgreSQL 9.6 zu installieren:
CREATE EXTENSION amcheck_next;
Wenn Sie die Fehlermeldung "Erweiterungssteuerungsdatei konnte nicht geöffnet werden" erhalten, prüfen Sie, ob Sie die korrekte Zielwartungsversion ausführen (POSTGRES_9_6_24.R20220710.01_12).
PostgreSQL 10 und höher
Führen Sie die folgende Anweisung aus, um amcheck
für PostgreSQL 10 und höher zu installieren:
CREATE EXTENSION amcheck;
Auf inkonsistente B-Baum-Indexe prüfen
In folgenden Abschnitten wird beschrieben, wie man auf inkonsistente B-Baum-Indexe prüft. Dazu werden Inkonsistenzen eines Index sowie eindeutige und Primärschlüsselverstöße geprüft.
Auf Inkonsistenzen prüfen
Führen Sie folgende Anweisung aus, um alle B-Baum-Indexe in jeder Ihrer Datenbanken auf Inkonsistenzen zu prüfen:
Code-Beispiel
DO $$ DECLARE r RECORD; version varchar(100); BEGIN RAISE NOTICE 'Started amcheck on database: %', current_database(); SHOW server_version into version; SELECT split_part(version, '.', 1) into version; FOR r IN SELECT c.oid, c.oid::regclass relname, i.indisunique FROM pg_index i JOIN pg_opclass op ON i.indclass[0] = op.oid JOIN pg_am am ON op.opcmethod = am.oid JOIN pg_class c ON i.indexrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE am.amname = 'btree' AND c.relpersistence != 't' AND c.relkind = 'i' AND i.indisready AND i.indisvalid LOOP BEGIN RAISE NOTICE 'Checking index %:', r.relname; IF version = '10' THEN PERFORM bt_index_check(index => r.oid); ELSE PERFORM bt_index_check(index => r.oid, heapallindexed => r.indisunique); END IF; EXCEPTION WHEN undefined_function THEN RAISE EXCEPTION 'Failed to find the amcheck extension'; WHEN OTHERS THEN RAISE LOG 'Failed to check index %: %', r.relname, sqlerrm; RAISE WARNING 'Failed to check index %: %', r.relname, sqlerrm; END; END LOOP; RAISE NOTICE 'Finished amcheck on database: %', current_database(); END $$;
Die Ausgabe sollte in etwa so aussehen:
Ausgabe
NOTICE: Checking index t_pkey: NOTICE: Checking index t_i_key: WARNING: Failed to check index t_i_key: item order invariant violated for index "t_i_key" NOTICE: Checking index t_j_key: WARNING: Failed to check index t_j_key: item order invariant violated for index "t_j_key" NOTICE: Checking index ij: WARNING: Failed to check index ij: item order invariant violated for index "ij"
Weitere Informationen zum Aufrufen von PostgreSQL-Logs finden Sie unter Instanzlogs ansehen.
Verstöße gegen eindeutige und Primärschlüssel identifizieren und beheben
In diesem Abschnitt wird beschrieben, wie Sie Ihren Index auf Verstöße gegen eindeutige und Primärschlüssel prüfen und gegebenenfalls beheben können.
Eindeutige Schlüsselverstöße identifizieren
Verstöße gegen eindeutige Schlüssel müssen behoben werden, bevor Sie einen Index neu indexieren. Führen Sie folgenden Befehl in jeder einzelnen Datenbank aus, um nach eindeutigen Verstößen zu suchen:
Code-Beispiel
WITH q AS ( /* this gets info for all UNIQUE indexes */ SELECT indexrelid::regclass as idxname, indrelid::regclass as tblname, indcollation, pg_get_indexdef(indexrelid), format('(%s)',(select string_agg(quote_ident(attname), ', ') from pg_attribute a join unnest(indkey) ia(nr) on ia.nr = a.attnum where attrelid = indrelid)) as idxfields, COALESCE(substring(pg_get_indexdef(indexrelid) FROM '[)] (WHERE .*)$'), '') as whereclause FROM pg_index WHERE indisunique /* next line excludes indexes not affected by collation changes */ AND trim(replace(indcollation::text, '0', '')) != '' ) SELECT /* the format constructs the query to execute for each index */ format( $sql$ DO $$ BEGIN RAISE NOTICE 'checking index=%3$I on table=%1$I key_columns=%2$I '; END;$$; SELECT this, prev, /* we detect both reversed ordering or just not unique */ (CASE WHEN this = prev THEN 'DUPLICATE' ELSE 'BACKWARDS' END) as violation_type FROM (SELECT %2$s AS this, lag(%2$s) OVER (ORDER BY %2$s) AS prev FROM %1$s %4$s ) s WHERE this <= prev and this IS NOT NULL and prev IS NOT NULL; /* change to just '<' if looking for reverse order in index */ $sql$, tblname, idxfields, idxname, whereclause ) FROM q -- LIMIT 20 /* may use limit for testing */ -- the next line tells psql to executes this query and then execute each returned line separately \gexec
Die Ausgabe dieses Skripts sieht in etwa so aus:
Ausgabe
NOTICE: checking index=users_email_key on table=users key_columns="(email)" NOTICE: checking index=games_title_key on table=games key_columns="(title)" this | prev | violation_type --------------------+--------------------+---------------- Game #16 $soccer 2 | Game #16 $soccer 2 | DUPLICATE Game #18 $soccer 2 | Game #18 $soccer 2 | DUPLICATE Game #2 $soccer 2 | Game #2 $soccer 2 | DUPLICATE Game #5 $soccer 2 | Game #5 $soccer 2 | DUPLICATE
In dieser Ausgabe zeigt der Tabellen-Header NOTICE
den Index, die Spalte und die Tabelle für die darunter angezeigten Werte. Wenn Ihre Ausgabe Zeilen mit DUPLICATE
oder BACKWARDS
enthält, weist dies auf Beschädigungen im Index hin und muss möglicherweise korrigiert werden. Zeilen mit BACKWARDS
weisen auf mögliche doppelte Werte hin, die eventuell ausgeblendet sind. Wenn einer dieser Einträge in der Tabelle angezeigt wird, finden Sie weitere Informationen unter Verstöße vom Typ "Schlüsselduplikat" beheben.
Verstöße vom Typ "Schlüsselduplikat" beheben
Wenn Sie einen doppelten eindeutigen Index identifiziert haben oder eine Neuindexierung aufgrund eines Verstoßes gegen die doppelten Schlüssel fehlschlägt, führen Sie die folgenden Schritte aus, um die doppelten Schlüssel zu finden und zu entfernen.
Extrahieren Sie
key_columns
aus dem TabellenheaderNOTICE
, wie in der vorherigen Beispielausgabe gezeigt. Im folgenden Beispiel lautet die Schlüsselspalteemail
.Code-Beispiel
NOTICE: checking index=users_email_key on table=users key_columns="(email)"
Verwenden Sie diese Werte in KEY_COLUMNS in der Abfrage in Schritt 3.
Suchen Sie das Schema für Ihre Tabelle. Stellen Sie mit
psql
eine Verbindung zu Ihrer Datenbank her und führen Sie den folgenden Befehl aus:Code-Beispiel
Der Wert in der\dt TABLE_NAME
schema
-Spalte ist der Wert, den Sie für SCHEMA_NAME in der Abfrage in Schritt 3 verwenden.Zum Beispiel für die folgende Abfrage:
\dt games
Die Ausgabe sieht in etwa so aus:
List of relations Schema | Name | Type | Owner --------+-------+-------+---------- public | games | table | postgres (1 row)
Führen Sie die folgenden Anweisungen aus, um einen vollständigen Tabellenscan zu erzwingen und Schlüsselduplikate abzurufen.
Code-Beispiel
SET enable_indexscan = off; SET enable_bitmapscan = off; SET enable_indexonlyscan = off; SELECT KEY_COLUMNS, count(*) FROM SCHEMA_NAME.TABLE_NAME GROUP BY KEY_COLUMNS HAVING count(*) > 1;
In der obigen Anweisung sind KEY_COLUMNS eine oder mehrere Spalten, die vom eindeutigen Index oder vom Primärschlüssel in der zu prüfenden Tabelle abgedeckt werden. Diese wurden bei der Prüfung auf eindeutige Schlüsselverstöße erkannt. Die Anweisung gibt die Schlüsselduplikate und die Anzahl der Duplikate zurück.
Zum Beispiel für die folgende Abfrage:
SELECT name,count(*) FROM public.TEST_NAMES GROUP BY name HAVING count(*) > 1;
Die Ausgabe sieht in etwa so aus:
name | count --------------------+------- Johnny | 2 Peter | 2 (2 rows)
Fahren Sie in diesem Fall mit dem nächsten Schritt fort, um die Schlüsselduplikate zu entfernen.
Wenn eine der Spalten in KEY_COLUMNS null ist, können Sie sie ignorieren, da für NULL-Spalten keine eindeutigen Einschränkungen gelten.
Wenn keine Schlüsselduplikate gefunden werden, können Sie mit Inkonsistente Indexe korrigieren fortfahren.
Optional, aber empfohlen: Erstellen Sie eine Sicherung der Datensätze mit Schlüsselduplikaten. Führen Sie die folgende Anweisung aus, um Sicherungsdatensätze zu erstellen:
Code-Beispiel
CREATE TABLE SCHEMA_NAME.TABLE_NAME_bak AS SELECT * FROM SCHEMA_NAME.TABLE_NAME WHERE (KEY_COLUMNS) IN ((KEY_VALUES));
In dieser Anweisung ist KEY_VALUES eine Liste an Werten, die aus dem Ergebnis des vorherigen Schritts kopiert wurden. Beispiel:
Code-Beispiel
CREATE TABLE public.TEST_NAMES_bak AS SELECT * FROM public.TEST_NAMES WHERE (name) IN (('Johnny'),('Peter'))
Bei einer großen Anzahl von Zeilen ist es einfacher, den Parameter ((KEY_VALUES )) in der Anweisung
IN
mit der AnweisungSELECT
aus Schritt 2 ohne den vcount
zu ersetzen. Beispiel:Code-Beispiel
CREATE TABLE SCHEMA_NAME.TABLE_NAME_bak AS SELECT * FROM SCHEMA_NAME.TABLE_NAME WHERE (KEY_COLUMNS) IN ( SELECT (KEY_COLUMNS) FROM SCHEMA_NAME.TABLE_NAME GROUP BY (KEY_COLUMNS) HAVING count(*) > 1);
Fügen Sie dem Nutzer eine Replikationsrolle hinzu, um Trigger zu deaktivieren:
Code-Beispiel
ALTER USER CURRENT_USER with REPLICATION; SET session_replication_role = replica;
Führen Sie die folgende Anweisung aus, um die Schlüsselduplikate zu löschen:
Code-Beispiel
BEGIN; DELETE FROM SCHEMA_NAME.TABLE_NAME a USING ( SELECT min(ctid) AS ctid, KEY_COLUMNS FROM SCHEMA_NAME.TABLE_NAME GROUP BY KEY_COLUMNS HAVING count(*) > 1 ) b WHERE a.KEY_COLUMNS = b.KEY_COLUMNS AND a.ctid <> b.ctid;
Zum Beispiel für KEY_COLUMNS mit mehreren Spalten:
Code-Beispiel
Dabei sind Tag und rnum KEY_COLUMNS.DELETE FROM public.test_random a USING ( SELECT min(ctid) AS ctid, day, rnum FROM public.test_random GROUP BY day, rnum HAVING count(*) > 1 ) b WHERE a.day=b.day and a.rnum = b.rnum AND a.ctid <> b.ctid;
Wenn Sie diese Anweisung ausführen, wird eine Zeile beibehalten und andere werden für jeden Satz doppelter Zeilen gelöscht. Wenn Sie steuern möchten, welche Version der Zeile gelöscht wird, führen Sie folgenden Filter in der Löschanweisung aus:
Code-Beispiel
DELETE FROM SCHEMA_NAME.TABLE_NAME WHERE ( KEY_COLUMNS, ctid) = (KEY_VALUES, CTID_VALUE);
Führen Sie folgende Schritte aus, um zu prüfen, ob der
DELETE
-Befehl die erwartete Anzahl von Zeilen ohne Fehler zurückgegeben hat:Führen Sie folgende Anweisung aus, um die Zeilen zu ermitteln, in denen die Tabellen geändert wurden:
Code-Beispiel
SELECT schemaname, relname, n_tup_del, n_tup_upd FROM pg_stat_xact_all_tables WHERE n_tup_del+n_tup_upd > 0;
Wenn alle Zeilen korrekt sind, führen Sie den Commit der
DELETE
-Transaktion durch:Code-Beispiel
END;
Falls Fehler auftreten, führen Sie ein Rollback der Änderungen durch, um die Fehler zu beheben:
Code-Beispiel
ROLLBACK;
Nachdem die Schlüsselduplikate gelöscht wurden, können Sie den Index neu indexieren.
Inkonsistente Indexe beheben
In den folgenden Abschnitten wird beschrieben, wie Sie Indexinkonsistenzen in Ihrer Instanz beheben können.
Abhängig von der Konfiguration Ihrer Datenbank müssen Sie möglicherweise für jeden in den vorherigen Schritten identifizierten Index die folgenden Schritte ausführen:
Wenn der Neuindexierungsvorgang aufgrund von Fremdschlüsselverstößen fehlschlägt, müssen Sie diese Verstöße finden und beheben.
Führen Sie den Vorgang zur Neuindexierung noch einmal aus.
Neuindexierung Ihres Index vorbereiten
Indexgröße ermitteln
Das Indexieren größerer Datenbanken erfordert mehr Zeit als das kleinerer Datenbanken. Um die Geschwindigkeit von Index- und Neuindexierungsvorgängen größerer Datenbanken zu verbessern, können Sie diesen Vorgängen mehr Arbeitsspeicher und CPU-Leistung zuweisen. Dies ist ein wichtiger Schritt bei der Planung Ihres Neuindexierungsvorgangs. Nachdem Sie die Indexgröße kennen, können Sie die vom Neuindexierungsvorgang verwendete Speichergröße festlegen und die Anzahl der parallelen Worker festlegen.
Führen Sie die folgende Anweisung aus, um die Größe des Index, den Sie korrigieren möchten, in Kilobyte zu ermitteln:
Code-Beispiel
SELECT i.relname AS index_name, pg_size_pretty(pg_relation_size(x.indexrelid)) AS index_size FROM pg_index x JOIN pg_class i ON i.oid = x.indexrelid WHERE i.relname = 'INDEX_NAME';
Die Ausgabe dieser Anweisung sieht so aus:
Ausgabe
index_name | index_size ------------+------------ my_index | 16 kB (1 row)
Speichergröße für die Neuindexierung festlegen
Abhängig von der Größe des Index, wie im vorherigen Abschnitt beschrieben, ist es wichtig, einen geeigneten Wert für maintenance_work_mem
festzulegen. Dieser Parameter gibt die Speichermenge an, die für die Neuindexierung verwendet werden soll. Wenn Ihre Indexgröße beispielsweise größer als 15 GB ist, empfehlen wir, den Wartungsspeicher anzupassen. Weitere Informationen finden Sie unter Datenbank-Flag festlegen.
Das Indexieren größerer Datenbanken erfordert mehr Zeit als das kleinerer Datenbanken. Um die Geschwindigkeit von Index- und Neuindexierungsvorgängen zu verbessern, empfehlen wir, maintenance_work_mem
während dieses Neuindexierungsvorgangs auf mindestens 2 % des Instanzspeichers für Instanzen mit 4 GB oder mehr Arbeitsspeicher zu setzen.
Anzahl paralleler Worker festlegen
Sie können die Anzahl der parallelen Worker für die Neuindexierung erhöhen, indem Sie den Parameter max_parallel_maintenance_workers in Datenbanken mit PostgreSQL 11 oder höher festlegen. Der Standardwert dieses Parameters ist 2, kann aber auf einen höheren Wert gesetzt werden, um die Anzahl der Worker für die Neuindexierung zu erhöhen. Bei Instanzen mit 8 oder mehr vCPU-Kernen empfehlen wir, den Wert des max_parallel_maintenance_workers
-Flags auf 4 zu setzen.
Weitere Informationen finden Sie unter Datenbank-Flag festlegen.
Index neu indexieren.
Sie können Indexe mit dem Dienstprogramm pg_repack
neu indexieren, ohne Ihre Produktionsarbeitslast zu blockieren. Dieses Dienstprogramm automatisiert und vereinfacht den gleichzeitigen Neuindexierungsprozess, sodass Sie eine Neuindexierung ohne Ausfallzeiten durchführen können, insbesondere für PostgreSQL-Versionen 11 und früher, die den Vorgang REINDEX CONCURRENTLY
nicht haben. Verwenden Sie für dieses Verfahren die pg_repack
-Version 1.4.7.
Führen Sie folgende Schritte aus, um Ihren Index mit pg_repack
neu zu indexieren:
Laden Sie das Dienstprogramm
pg_repack
von der Seite „pg_repack” herunter, kompilieren und installieren Sie es.Debian GNU/Linux 11
Der Einfachheit halber sollten Debian Linux-Nutzer dieses vordefinierte ausführbare Binärprogramm für die Linux x86_64-Plattform herunterladen und installieren.
Der sha256-Prüfsumme-Hash des Binärprogramms ist:
ecfee54364a625d9365d86cb27940b458bfdb0d6ff63bb88063039256fbde96f
Prüfen Sie mit dem Befehl
hostnamectl
, ob Ihre Linux-Version Debian GNU/Linux 11 ist.Selbstkompilieren
Laden Sie das Dienstprogramm
pg_repack
von der Seitepg_repack
herunter, kompilieren und installieren Sie es.Erstellen Sie die Erweiterung
pg_repack
:Code-Beispiel
CREATE EXTENSION pg_repack;
Führen Sie folgenden Befehl aus, um Ihren Index gleichzeitig neu zu indexieren:
Code-Beispiel
pg_repack -h HOSTIP -p 5432 -U USERNAME -d "DATABASE_NAME" -i "INDEX_NAME" --no-superuser-check --no-kill-backend --wait-timeout=3600
Die Ausgabe dieses Befehls sieht in etwa so aus:
Ausgabe
INFO: repacking index "public.t_i_key"
Wenn beim Ausführen von
pg_repack
Fehler aufgetreten sind, können Sie sie beheben und es noch einmal versuchen. Nachdem Sie alle eindeutigen Schlüsselindexe und Primärschlüsselindexe korrigiert haben, sollten Sie nach Fremdschlüsselverstößen suchen und etwaige Fehler beheben.
Fremdschlüsselverstöße ermitteln und beheben
Informationen zum Suchen und Beheben von Verstößen gegen Fremdschlüssel finden Sie unter Fremdschlüsselverstöße ermitteln und beheben.
Neuindexierungsvorgänge überwachen
Gelegentlich kann der Neuindexierungsvorgang durch andere Sitzungen blockiert werden. Wir empfehlen, dies alle vier Stunden zu prüfen. Wenn der Neuindexierungsvorgang blockiert ist, können Sie die Blockierungssitzung abbrechen, damit der Neuindexierungsvorgang abgeschlossen werden kann.
Führen Sie folgende Schritte aus, um blockierende und wartende Sitzungen zu identifizieren und sie dann im INDEX-Vorgang abzubrechen:
Führen Sie die folgende Abfrage aus, um blockierende Sitzungen zu ermitteln:
Code-Beispiel
SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by, query AS blocked_query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;
Führen Sie zum Abbrechen einer Sitzung die folgende Abfrage mit der PID der blockierenden Sitzung aus der vorherigen Abfrage aus:
Code-Beispiel
SELECT pg_cancel_backend(PID);
Prüfen, ob die Indexe konsistent sind
Prüfen Sie bei jedem einzelnen inkonsistenten Index weiterhin auf Indexinkonsistenzen. Nachdem Sie alle inkonsistenten Indexe und Schlüsselverstöße Ihrer Instanz behoben haben, können Sie prüfen, ob Probleme vorliegen. Dazu führen Sie die Schritte in den vorherigen Abschnitten aus: