Wie rettet man Daten aus eine MySQL Innodb Tabelle?

Dieses Szenario basiert drauf das die Tabelle noch lesbar ist und nur einige Datensätze defekt sind.
Ich musste dies schmerzhaft feststellen als ich versuchte meinen Mail Server auf einen neuen Server zu transferieren und dabei Probleme auftraten. Ich versuchte mittels xtraBackup ein Backup der Dbmail Datenbank anzulegen und bekam folgende Fehlermeldung:
Copying ./dbmail/dbmail_messageblks.ibd
to /battlefield/arsenal/backup/2012-02-12_16-36-30/dbmail/dbmail_messageblks.ibd
>> log scanned up to (454027191026)
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Database page corruption detected at page 8508. retrying...
xtrabackup: Error: 10 retries resulted in fail. This file seems to be corrupted.
xtrabackup: Error: xtrabackup_copy_datafile() failed.
xtrabackup: Warining: failed to copy, but continuing.

Das bedeutet soviel wie: Einer oder mehrere Datensätze konnten nicht gelesen werden, warum auch immer, wahrscheinlich ein Hardwarefehler.
Da die Maschine produktiv lief und permanent neue Mails reinkamen kopierte ich die gesamte MySQL Datenbank mittels netcat auf den neuen Server um dort ungestört meine Reparaturmaßnahmen durchführen zu können.
Neuer Server:
neuerserver:/datenbankverzeichnis/nc -l -p 333 | tar xv
alterserver:/etc/init.d/mysql stop
alterserver:/datenbankverzeichnis/tar cv * | pv | nc 333

alterserver:/etc/init.d/mysql start
(Der pv command ist nicht notwendig er zeigt nur den fortschritt an, also solltet ihr diesen nicht installiert haben macht das nichts einfach „pv |“ weglassen)
Nach dem das Kopieren fertig ist (dauerte bei mir 1,5) Stunden, kann auf dem neuen Server das bestehende MySQL Datenbank Verzeichnis umbenannt werden und die neuen Daten in das Verzeichnis rein verschoben werden.
Wichtig die alte/neue MySQL Datenbank muss die richtigen Rechte besitzen, damit sie vom MySQL Server gelesen werden kann.
Nach dem das erledigt ist können wir den MySQL Server starten und überprüfen ob wir auf den Datenbank zugreifen können.
Ich gehe mal von ja aus ;-)
Da der MySQL Thread abstürzt, wenn wir versuchen auf die beschädigten Datensätze zuzugreifen, müssen wir MySQL in den Recoverymodus versetzen. Dazu schreiben wir in die my.cnf im Bereich [mysqld] folgende Zeile:
innodb_force_recovery = 1 alles bis 4 dürfte ok sein, darüber könnte es zu mehr Datenverlust gekommen sein lt. MySQL Dokumentation.
Das Problem am Recoverymodus ist das es nicht möglich ist eine InnoDB Tabelle zu verändern, das bedeutet wir müssen eine 2te Tabelle anlegen und zwar als MyISAM, damit können wir diese Limitierung umgehen.
In meinem Fall sieht das Query so aus:
CREATE TABLE `dbmail`.`dbmail_messageblks_recover` (
`messageblk_idnr` bigint( 21 ) NOT NULL AUTO_INCREMENT ,
`physmessage_id` bigint( 21 ) NOT NULL DEFAULT '0',
`messageblk` longblob NOT NULL ,
`blocksize` bigint( 21 ) NOT NULL DEFAULT '0',
`is_header` tinyint( 1 ) NOT NULL DEFAULT '0',
PRIMARY KEY ( `messageblk_idnr` ) ,
KEY `physmessage_id_index` ( `physmessage_id` ) ,
KEY `physmessage_id_is_header_index` ( `physmessage_id` , `is_header` )
) ENGINE = MYISAM DEFAULT CHARSET = latin1;

Ich hab phpmyadmin dazu bemüht mir die Tabelle zu kopieren um das CREATE Query zu bekommen, leider kann man nicht nur die Struktur kopieren da dann die ENGINE=InnoDB mit kopiert wird und das lässt sich im Nachhinein nicht mehr ändern auf Grund des Readonly Zugriffs auf InnoDB Tabellen.
Als nächstes habe ich ein kleines Shell Script geschrieben welches immer nur eine kleine Anzahl an Datensätzen kopiert.
#!/bin/bash

backupuser=USERNAME
backuppassword=PASSWORT

#Findet den letzten Datensatz in der wiederhergestellten Tabelle
BEGIN=`echo "SELECT messageblk_idnr as ' ' FROM dbmail_messageblks_recover ORDER BY messageblk_idnr DESC limit 1;" | mysql -u ${backupuser} --password=${backuppassword} -D dbmail -B `
IFS="
"
if [ "$BEGIN" = "" ];
then
BEGIN=0
fi
#Wie viele Datensaetze sollen auf einmal kopiert werden?
INTERVAL=50
#Was die der letzte Datensatz ID
END=7413082
for a in `seq $BEGIN $INTERVAL $END`
do
echo "copy ids ${a}"
echo "INSERT INTO dbmail_messageblks_recover select * from dbmail_messageblks where messageblk_idnr > ${a} and messageblk_idnr <= ${a}+${INTERVAL}" | mysql -u ${backupuser} --password=${backuppassword} -D dbmail if [ "$?" != "0" ]; then exit 1 fi done

Bei diesem Script müssen ein paar Werte angepasst werden dann sollte es schon mal so lange laufen bis es einen Fehler gibt. Sobald ein Fehler auftritt erstellt man eine zweite Recover Tabelle und ändert den Beginn Wert auf die letzte id+1000 und ändert die Zieltabelle in recover2 und lasst das Script nochmal drüber laufen. Sollte jetzt bis zum Ende kein Fehler mehr auftreten, kann man davon ausgehen das das Problem zwischen id und id+1000 liegt.
Um jetzt auch noch die letzten funktionsfähigen Datensätze zu bekommen ändern wir das Script ein weiteres mal, aber jetzt wird BEGIN auf id gesetzt und Interval auf 1 und das END auf id+1000 sowie die Recover Tabelle auf recover3 (aja die muss man noch vorher anlegen ;-). Weiters sollte man noch das "exit 1" ersetzen mit "echo $a" damit man die Fehler sieht.
Das ausführen wird zu Fehlern führen aber mit ein bisserl Glück kann er alle Datensätze, bis auf die beschädigten, kopieren.
Am ende fügen wir die 3 Tabellen in eine zusammen und haben hoffentlich wieder eine funktionierende Datenbank. Es kann vorkommen das die MyISAM Recover Tabellen als kaputt markiert werden, man muss sie dann mittels "Repair table" reparieren.

PS: Der Recoverymodus muss wieder ausgeschaltet werden.

Notice: This work is licensed under a BY-NC-SA. Permalink: Wie rettet man Daten aus eine MySQL Innodb Tabelle?

Comments are closed.