In den letzten Tagen untersuchten wir eine defekte Datenbank eines Kunden. DBCC CHECKDB bracht nur die vielsagende Meldung, dass eine der internen Verwaltungstabellen defekt sei und man deswegen eine Sicherung einspielen solle. Nun hatte der Kunde aber keine aktuelle Sicherung (die letzte war von April). Wir konnten dem SQL Server nicht auf dokumentierte Weise entlocken wo er Defekt nun war.

Mit einer gezielten Prüfung auf die versteckten Systemtabellen fanden wird schnell den Bösewicht. Wer sich auch mal mit dem Thema befassen will, findet im Folgenden eine Liste der internen Tabellen (mit DBCC CHECKTABLE), die Schritte die zur Reparatur führten und die Ursachenanalyse. Warnung: Nur wen das interessiert, sollte ab hier weiter lesen… 😉

DBCC CHECKTABLE('sys.sysallocunits') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysasymkeys') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysbinobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysbinsubobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syscerts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysclsobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syscolpars') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysconvgroup') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysdbfiles') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysdercv') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysdesend') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysfiles1') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysftinds') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysguidrefs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syshobtcolumns') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syshobts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysidxstats') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysiscols') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysmultiobjrefs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysnsobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysobjkeycrypts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysobjvalues') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysowners') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysprivs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysqnames') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysremsvcbinds') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrowsetcolumns') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrowsetrefs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrowsets') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysscalartypes') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysschobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmitqueue') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmlcomponent') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmlfacet') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmlplacement') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxprops') WITH NO_INFOMSGS;

Die Prüfung sagte uns nicht nur, welche Tabelle defekt war (sys.sysallocunits), sondern auch auf welcher Seite in welchem Slot (=Datensatz) das Problem sei. Mit DBCC PAGE oder dem SQL Server Internals Viewer konnten wir leicht sehen, dass ein Wert in dieser Slot völlig untypisch war. Tatsächlich war ein Bitkipper passiert, wir mussten uns nun lediglich den Offset des gekippten Bytes innerhalb der Seite merken und damit dann das Problem komplett beheben:

Mit einem Hex-Editor gingen wir an die Adresse des bösen Bytes (Formel = Seite*8192+Offset in der Seite) und korrigierten den Wert. Danach war die Datenbank wieder völlig in Ordnung. Nun hatten wir natürlich Glück, dass wir anhand eines Vergleichs des bösen Datensatzes mit Korrekten das Problem mit bloßem Auge erkennen konnten. Aber gerade deswegen hat mich das in Hochstimmung versetzt… 😀

Risiken und Nebenwirkungen
Ein Hinweis noch: Am SQL-Server-2005 muss man erst noch die Seiten-Checksummen ausschalten, sonst meckert der SQL-Server, wenn die DB nach der Änderung wieder angehängt wird.
ALTER DATABASE
SET PAGE_VERIFY NONE

Nach dem Eingriff kann man die Checksumme wieder einschalten:
ALTER DATABASE
SET PAGE_VERIFY CHECKSUM

Außerdem sollte man vor solchen Aktionen immer eine Kopie der Dateien machen. Wenn man das falsche Byte erwischt, dann ist die Datenbank erst recht defekt… 😉

Ursachenforschung

Die Checksummenprüfung des SQL-Server (seit 2005) erkennt, wenn Bitkipper aufgrund von Problemen im Speichersystem auftreten, nicht aber im Hauptspeicher auftretende Probleme. Das geht so: Der SQL-Server ändert die Datenseiten im Hauptspeicher ständig, deswegen wird da nicht jedesmal eine Checksumme berechnet, das würde einfach zu lange dauern.

Wird die geänderte Seite auf die Platte geschrieben, dann wird eine Checksumme berechnet und auf der betreffenden Seite mit gespeichert. Beim Lesen wird die Checksumme ebenfalls berechnet und auf Abweichungen kontrolliert. Bei Abweichungen muss ein Problem im Speichersystem aufgetreten sein (Treiber, defektes RAM im IO-Caching oder Festplattenproblem). Das war hier nicht der Fall.

Will man so eine Prüfung auch schon am SQL-Server-2000, dann muss man einen Hotfix einspielen und Trace-Flag 806 setzen.

So ein Bitkipper, wie oben beschrieben, muss daher im Hauptspeicher passiert sein. Deswegen wurde der Kunde darauf hingewiesen, dass sein RAM einen Schlag hat. Es wäre natürlich auch denkbar, dass der SQL-Server-Prozess selber dort Murks hingeschrieben hätte (das nennt man "Scribbler", z.B. die im Prozessraum laufenden Extended-Procedures), aber das ist in unserer Komstellation nicht wahrscheinlich.

Wenn man es ganeu wisse wollte, dann könnte man den SQL-Server eine Zeit lang mit dem Trace-Flag 806 laufen lassen. Damit wird auch im Hauptspeicher die Checksumme gelegentlich überprüft. Weil das aber die Performance ungeheuer beeinträchtigt, sollte man das nur für kurze Zeit machen…

Weiterführende Infos: