Wie ich eine kaputte Basis reparierte und was daraus wurde

Einmal wurde ich gebeten, eine Datenbank zu reparieren. CHECKDB hat während der Überprüfung eine Liste von Fehlern ausgegeben, von denen einige als "nicht korrigierbar" markiert wurden. Die Anwendung funktionierte, war aber immer noch unruhig.

Ja, die richtige Lösung in einer solchen Situation wäre, ab dem Moment, in dem die Fehler immer noch nicht auftreten, ein Backup zu erstellen, die beschädigten Daten zu lokalisieren und sie von einer sauberen Kopie zu überschreiben. Aber ... wie so oft wurde der Fehler zu spät abgefangen, so dass es wirklich nichts gab, von dem man sich erholen konnte. Auf der anderen Seite - es gäbe ein Backup, es gäbe diese Geschichte nicht.

Anamnese


Der erste Schritt besteht darin, DBCC CHECKDB zu starten, um das Ausmaß der Tragödie zu verstehen. Das Team durchlief alle Tische ehrlich und stellte größtenteils keine Probleme fest. In der gleichen Schlussfolgerung gab es hundert Berichte über "korrigierbare" Fehler. Ungefähr so:

Index row (1:386974:44) with values (C_FK_6bb5032ec2f94557a7d4a9d39a356168 = '04DA7FC4-B8F2-4D97-B8D2-B207A918D3DF' and C_PK_dd87d9cad5504a1199d4ddaf511ea6a9 = 'F44E8C34-862D-4952-8821-D0E87143A74E') pointing to the data row identified by (C_PK_dd87d9cad5504a1199d4ddaf511ea6a9 = 'F44E8C34-862D-4952-8821-D0E87143A74E'). 

Und noch ein paar gravierende Fehler:

 Table error: Object ID 1333579789, index ID 1, partition ID 72057609974841344, alloc unit ID 72057610075701248 (type In-row data). Page (1:20426) is missing a reference from previous page (1:267203). Possible chain linkage problem. 

Na dann. Der Arbeitsumfang ist skizziert, los geht's!

Behebbare Fehler


Um zu verstehen, warum einige Fehler leicht automatisch behoben werden können, erinnern wir uns, wie die Indizes in MS SQL angeordnet sind. Sie können sie in zwei Typen unterteilen: Cluster und (überraschenderweise) Nicht-Cluster. (Wir werden uns nicht mit speziellen Themen wie Columnstore-Indizes befassen - dies ist nicht der Fall.) Bei beiden handelt es sich um einen ausgeglichenen Baum, der sich sehr gut zum Auffinden von Daten eignet.

Es ist wichtig, dass Clusterindizes auf Blattebene den Inhalt von Tabellenzeilen direkt speichern. Nicht geclusterte Indizes speichern jedoch nur die Schlüsseldaten (und, falls verfügbar, die eingeschlossenen Felder) sowie einen Link zur Clusterindexzeile. Das heißt, wenn wir ein Problem in einem nicht gruppierten Index haben, können wir einfach die beschädigten Daten aus dem gruppierten Index entnehmen und überschreiben. Nun, oder erstellen Sie einfach den überholten Index neu - es ist gut, die Zusammensetzung der Felder ist bekannt und die Quelldaten sind in der Nähe, intakt.

Die Aufgabe ist absolut nicht kreativ, sodass Sie sie sicher einem seelenlosen Auto anvertrauen können. Führen Sie den Befehl aus

 DBCC CHECKDB (< >, REPAIR_REBUILD) 

und studiere den Fortschrittsbericht.

Sowohl das Quellprotokoll als auch das Protokoll nach dem "Fix" enthalten die Adressen beschädigter Seiten. Wir vergleichen diese Adressen und stellen sicher, dass alle als "korrigierbar" gekennzeichneten Fehler tatsächlich erfolgreich behoben wurden.

Datenverbindungsstörung


Jetzt etwas Ernstes. Nach dem Korrigieren von nicht gruppierten Indizes und dem Löschen des Protokolls von Informationsnachrichten enthält der Bericht drei Datensätze mit „schwerwiegenden“ Fehlern

Sie können natürlich mit dem Befehl DBCC CHECKDB (<DB-Name>, REPAIR_ALLOW_DATA_LOSS) aufgeben und abhacken. Aber ... ich möchte die Daten einfach nicht verlieren. Ich möchte alles, was möglich ist, maximal wiederherstellen. Lassen Sie uns daher genauer sehen, was der Fehlerbericht im Allgemeinen aussagt.

 Table error: Object ID 1333579789, index ID 1, partition ID 72057609974841344, alloc unit ID 72057610075701248 (type In-row data). Page (1:20426) is missing a reference from previous page (1:267203). Possible chain linkage problem. Table error: Object ID 1333579789, index ID 1, partition ID 72057609974841344, alloc unit ID 72057610075701248 (type In-row data). The previous link (1:267203) on page (1:267204) does not match the previous page (1:20426) that the parent (1:218898), slot 213 expects for this page. Table error: Object ID 1333579789, index ID 1, partition ID 72057609974841344, alloc unit ID 72057610075701248 (type In-row data). B-tree chain linkage mismatch. (1:20426)->next = (1:267204), but (1:267204)->Prev = (1:267203). 

Der Bericht enthält eine düstere Geschichte, von der eine Seite erwartet, dass sie einen Nachbarn sieht, aber der Nachbar weiß nichts darüber. Ein pelevinsky Seemann Zheleznyak: ging an Deck, aber kein Deck.

Für ein vollständiges Verständnis sind weitere Details erforderlich, die direkt zum Inhalt der Seiten führen müssen. Vorher werden wir jedoch herausfinden, wie die SQL Server-Indexseiten im Allgemeinen zusammenhängen.

Offensichtlich haben die Seiten hierarchische „vertikale“ Links, die den B-Baum bilden. Die obere Seite enthält Links zu Seiten einer niedrigeren Ebene und so weiter zu den Blättern. Wie ich bereits sagte, ist dies sehr praktisch, um Werte zu finden: Sie möchten „Vasya Pupkin“ suchen und nach ein paar Seiten („von B nach G“ → „von Ba nach B“ → „Vasya Pupkin“) finden Sie die gewünschte.

Es gibt jedoch Situationen, in denen eine Abfrage sofort ganze Zeilenbereiche auswählen muss ("Von Vasya nach Grisha"). In diesem Fall laufen Sie jedes Mal, wenn Sie den Baum von oben nach unten hinuntergehen, in ihn hinein. Um solche Anforderungen zu erfüllen, speichern Seiten "horizontale" Links: Jede Seite kennt die Nachbarnummer "vor" und "nach". Mit diesen Beziehungen sind Index-Scans viel einfacher durchzuführen.

Dem Fehlerprotokoll nach zu urteilen, war die Übereinstimmung von horizontalen und vertikalen Verbindungen nicht gegeben. Aber um sich endlich davon zu überzeugen, schauen wir uns die Seiten selbst an.

Wir müssen tiefer gehen!


Zum Anzeigen der Seiten verwenden wir das alte, wohlverdiente und undokumentierte DBCC PAGE-Team. Es werden 4 Parameter benötigt:

  • Eid Base
  • Basisdatei-ID
  • Seiten-ID
  • Detaillierungsgrad (0 bis 3)

Abhängig vom letzten Parameter können Sie entweder nur die Service-Überschrift (0) oder den gesamten Inhalt der Seite (3) oder die Überschrift und einen Teil des Inhalts (1 und 2) sehen.

Übrigens ist in SQL Server 2019 endlich eine dokumentierte Ansicht sys.db_db_page_info erschienen, die ähnliche Aufgaben ausführt. Leider werden nur die Kopfdaten angezeigt (analog zu Aufriss 0), sodass unsere Aufgaben immer noch nicht vollständig beantwortet werden.

Führen Sie zunächst den Befehl aus

 DBCC TRACEON (3604, 1) 

damit die Ausgabe der restlichen DBCC-Befehle zu unserer Konsole und nicht zu ErrorLog gelangt

Schauen Sie sich danach den Seitentitel 20426 an:

 DBCC PAGE (11, 1, 20426, 0) 

Bild

Ich verstehe Die Seite soll in der Mitte zwischen den Seiten 267203 und 267204 liegen. Aber was ist mit denselben Seiten?

Bild

Bild

Kein Schlaf, kein Geist! Sie sind gut ohne ungebetene Gäste.
Schauen wir uns das kommende Inhaltsverzeichnis an:

Bild

Das Puzzle entwickelt sich allmählich:

  • In Bezug auf „vertikale“ Links (Indexbaumstruktur) sollte Seite 20426 zwischen 267203 und 267204 liegen
  • Die horizontalen Verbindungen widersprechen dies und sagen, dass es zwischen 267203 und 267204 niemanden gibt.

Versuchen wir nun zu verstehen, welche Art von Daten von dem Fehler betroffen waren.

Zeilen innerhalb der Seite werden nach Indexschlüssel sortiert. Wenn Sie den ersten und den letzten Schlüsselwert auf der Seite kennen, können Sie den Bereich der "betroffenen" Datensätze ermitteln.

Der Schlüssel kann einfach in der Beschreibung des Index eingesehen werden. Wir werden dafür den Befehl sp_helpindex verwenden . In diesem Fall ist der Schlüssel nur eine Spalte, inkl. schenke ihr alle Aufmerksamkeit.

Bild

Um die Schlüssel zu finden, die auf die Seite gelangt sind, verwenden wir DBCC PAGE mit maximal 3 Detaillierungsgraden. Wenn Sie durch das Ausgabeprotokoll scrollen, sehen Sie den "rohen" Inhalt jeder Zeile und eine Aufschlüsselung für jedes Feld:

Bild

Übrigens sind die Schlüsselgrenzwerte (der erste Wert auf der Seite) auch in der DBCC-PAGE-Ausgabe für die höhere Indexseite (218898, siehe Abbildung oben) zu sehen. Sie werden in der Spalte unmittelbar nach der Seitenzahl angezeigt.

Eine Überprüfung der Seiten zeigt, dass Seite 20426 genau die Hälfte der Zeilen von Seite 267203 enthält. Die Art des Fehlers wird klar. Wenn die Datenbankseite voll ist und kein Platz mehr zum Einfügen neuer Daten vorhanden ist, wird sie in zwei neue Seiten aufgeteilt. Als die Seite 267203 voll wurde, wurde anscheinend die Problemseite 20426 erstellt. Das DBMS begann mit der Neuerstellung seiner Links: Es konnte eine neue Seite in die Indexstruktur geschrieben werden. Aus irgendeinem Grund konnten die horizontalen Datensätze nicht aktualisiert werden und die neue Seite hing in einem undefinierten Zustand.

Was wird das alles für uns sein?


Es gibt sicher nicht viel Gutes. Seitenlinks werden offensichtlich benötigt, um auf Daten zuzugreifen. Bei der Ausführung der Abfrage legt das DBMS unabhängig fest, auf welche Weise genau diese Daten abgerufen werden sollen. Die allgemeine Regel (von der Ausnahmen möglich sind) lautet jedoch:

  • "Weitwinkel" -Auswahlen (zum Beispiel für Berichte) werden unter Verwendung horizontaler Datensätze durchgeführt. Es ist einfacher, mehrere Seiten nacheinander zu durchsuchen und einen großen Bereich auszuwählen
  • "Punkt" -Abfragen (Aktualisieren eines bestimmten Datensatzes) werden durch eine Suche nach "Inhaltsverzeichnis" durchgeführt.

Es stellt sich heraus, dass bestimmte Datensätze geändert werden und das DBMS zur Seite „Problem“ 20426 gelangt. Wenn der Bericht ausgeführt wird, werden die Daten „horizontal“ gelesen und die vorgenommenen Änderungen werden nicht angezeigt. Noch einmal: In der Praxis mag der Algorithmus komplizierter sein, aber die Klasse der möglichen Probleme ist immer noch verständlich.

Die ewigen Fragen des russischen Intellektuellen


Um ehrlich zu sein, ist mir immer noch nicht klar, wie das passieren könnte. Ein modernes DBMS ist eigentlich eine ziemlich zuverlässige Sache. Alle Änderungen in der Datenbankdatei (einschließlich der Anpassung der horizontalen und vertikalen Beziehungen) werden in einer Transaktion durchgeführt. Diese Vorgänge werden im Transaktionsprotokoll aufgezeichnet. Wenn in diesem Protokoll keine Bestätigung eines erfolgreichen Vorgangs vorhanden ist, werden alle Vorgänge rückgängig gemacht. Hier können Sie sehen, dass die Transaktion erfolgreich abgeschlossen wurde, aber einige der Änderungen entlang des Pfads zur Datendatei „verloren gegangen“ sind.

Die einzige plausible Erklärung, die mir einfiel, war ein Fehler im Cache des Festplattensubsystems. Alle Daten gingen in den Cache, dann wurde ein Teil der Datensätze aus der Datendatei und aus dem Protokoll auf die Festplatte geschrieben - und dann zog jemand den Schalter um. Infolgedessen konnten die geänderten "horizontalen" Datensätze nicht aufgezeichnet werden, aber die Datenbank wusste bereits nichts darüber. (Und hier sollten sich viele Leser beeilen, die Batterien ihrer industriellen Festplattencontroller zu überprüfen.)

Was zu tun ist, ist viel klarer. Um die Konnektivität einer Tabelle wiederherzustellen, genügt es, ihre Daten in eine andere Tabelle zu kopieren, damit beim Kopieren das horizontale Lesen verwendet wird. Für die Wiedergabetreue können Sie die richtige DBMS-Zugriffsmethode mithilfe des FORCESCAN-Hinweises explizit angeben

 select * into T_bca79e9e77c24cdc8bbb7cfd0ddc16fd_BKP from T_bca79e9e77c24cdc8bbb7cfd0ddc16fd with (FORCESCAN) 

Danach können Sie die Quelltabelle löschen und die kopierten Daten dort zurückgeben.

Die auf Seite 20426 vorgenommenen Änderungen gehen jedoch verloren. Daher müssen Sie vor dem Erstellen einer Kopie der Tabelle die Zeilen mit den Bezeichnern von Seite 20426 kopieren. Korrigieren Sie nach dem Wiederherstellen der Tabelle die erforderlichen Einträge. Der Zugriff auf die Datensätze von Seite 20426 wird explizit durch Bezeichner benötigt:

 select * from T_bca79e9e77c24cdc8bbb7cfd0ddc16fd where C_PK_bca79e9e77c24cdc8bbb7cfd0ddc16fd = '' 

Identifikatoren können erhalten werden, indem der Inhalt der Seite mit derselben DBCC-SEITE gelesen wird. Wie ich bereits schrieb, gab es genau die Hälfte dieser Seiten 267203, dh 15 Links.

Sichern Sie beschädigte Seiten, laden Sie die Tabelle neu und passen Sie die passenden Datensätze an - nach einigen Minuten wurde die Tabelle wiederhergestellt.

Hurra, wir haben gewonnen! Aber ist es wahr?


Die Wahrheit ist. Die Daten wurden wiederhergestellt, CHECKDB gab keine Fehler mehr aus, selbst die Sonne sah aus dem Fenster. Sie können sich sicher loben, ein wohlverdientes Glas eines Husarengetränks erheben und ... daran denken, dass die Daten in der Datenbank nicht nur mit Links zu Seiten verbunden sind. Also ist es Zeit, DBCC CHECKCONSTRAINTS aufzunehmen und kopfüber in die Liste der defekten Fremdschlüssel einzutauchen . Aber das ist eine ganz andere Geschichte ...

Was gibt es sonst noch zum Thema zu lesen


  • Befehlssyntax CHECKDB (Warnungen vor möglichen Risiken bei der Verwendung des Befehls beachten!)
  • Die inoffizielle Beschreibung von DBCC PAGE
  • Ein guter Artikel über MS SQL-Indizes, der viele interessante Dinge erklärt. Einschließlich der Speicherung physischer Indizes in der Datenbank

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


All Articles