{"id":287,"date":"2012-02-12T17:20:21","date_gmt":"2012-02-12T16:20:21","guid":{"rendered":"http:\/\/www.schoen-bloed.at\/blog\/?p=287"},"modified":"2012-04-01T17:29:10","modified_gmt":"2012-04-01T16:29:10","slug":"wie-rettet-man-daten-aus-eine-mysql-innodb-tabelle","status":"publish","type":"post","link":"https:\/\/www.schoen-bloed.at\/blog\/2012\/02\/wie-rettet-man-daten-aus-eine-mysql-innodb-tabelle\/","title":{"rendered":"Wie rettet man Daten aus eine MySQL Innodb Tabelle?"},"content":{"rendered":"<p>Dieses Szenario basiert drauf das die Tabelle noch lesbar ist und nur einige Datens\u00e4tze defekt sind.<!--more--><br \/>\nIch 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:<br \/>\n<code>Copying .\/dbmail\/dbmail_messageblks.ibd<br \/>\n     to \/battlefield\/arsenal\/backup\/2012-02-12_16-36-30\/dbmail\/dbmail_messageblks.ibd<br \/>\n>> log scanned up to (454027191026)<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Database page corruption detected at page 8508. retrying...<br \/>\nxtrabackup: Error: 10 retries resulted in fail. This file seems to be corrupted.<br \/>\nxtrabackup: Error: xtrabackup_copy_datafile() failed.<br \/>\nxtrabackup: Warining: failed to copy, but continuing.<\/code><br \/>\nDas bedeutet soviel wie: Einer oder mehrere Datens\u00e4tze konnten nicht gelesen werden, warum auch immer, wahrscheinlich ein Hardwarefehler.<br \/>\nDa die Maschine produktiv lief und permanent neue Mails reinkamen kopierte ich die gesamte MySQL Datenbank mittels netcat auf den neuen Server um dort ungest\u00f6rt meine Reparaturma\u00dfnahmen durchf\u00fchren zu k\u00f6nnen.<br \/>\n<code>Neuer Server:<br \/>\nneuerserver:\/datenbankverzeichnis\/nc -l -p 333 | tar xv<br \/>\nalterserver:\/etc\/init.d\/mysql stop<br \/>\nalterserver:\/datenbankverzeichnis\/tar cv * | pv | nc <neueserverip> 333<\/code><br \/>\nalterserver:\/etc\/init.d\/mysql start<br \/>\n(Der pv command ist nicht notwendig er zeigt nur den fortschritt an, also solltet ihr diesen nicht installiert haben macht das nichts einfach &#8222;pv |&#8220; weglassen)<br \/>\nNach 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.<br \/>\n<strong>Wichtig die alte\/neue MySQL Datenbank muss die richtigen Rechte besitzen, damit sie vom MySQL Server gelesen werden kann.<\/strong><br \/>\nNach dem das erledigt ist k\u00f6nnen wir den MySQL Server starten und \u00fcberpr\u00fcfen ob wir auf den Datenbank zugreifen k\u00f6nnen.<br \/>\nIch gehe mal von ja aus ;-)<br \/>\nDa der MySQL Thread abst\u00fcrzt, wenn wir versuchen auf die besch\u00e4digten Datens\u00e4tze zuzugreifen, m\u00fcssen wir MySQL in den Recoverymodus versetzen. Dazu schreiben wir in die my.cnf im Bereich [mysqld] folgende Zeile:<br \/>\n<code>innodb_force_recovery = 1<\/code> alles bis 4 d\u00fcrfte ok sein, dar\u00fcber k\u00f6nnte es zu mehr Datenverlust gekommen sein lt. MySQL Dokumentation.<br \/>\nDas Problem am Recoverymodus ist das es nicht m\u00f6glich ist eine InnoDB Tabelle zu ver\u00e4ndern, das bedeutet wir m\u00fcssen eine 2te Tabelle anlegen und zwar als MyISAM, damit k\u00f6nnen wir diese Limitierung umgehen.<br \/>\nIn meinem Fall sieht das Query so aus:<br \/>\n<code>CREATE TABLE `dbmail`.`dbmail_messageblks_recover` (<br \/>\n`messageblk_idnr` bigint( 21 ) NOT NULL AUTO_INCREMENT ,<br \/>\n`physmessage_id` bigint( 21 ) NOT NULL DEFAULT '0',<br \/>\n`messageblk` longblob NOT NULL ,<br \/>\n`blocksize` bigint( 21 ) NOT NULL DEFAULT '0',<br \/>\n`is_header` tinyint( 1 ) NOT NULL DEFAULT '0',<br \/>\nPRIMARY KEY ( `messageblk_idnr` ) ,<br \/>\nKEY `physmessage_id_index` ( `physmessage_id` ) ,<br \/>\nKEY `physmessage_id_is_header_index` ( `physmessage_id` , `is_header` )<br \/>\n) ENGINE = MYISAM DEFAULT CHARSET = latin1;<\/code><br \/>\nIch hab phpmyadmin dazu bem\u00fcht 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\u00e4sst sich im Nachhinein nicht mehr \u00e4ndern auf Grund des Readonly Zugriffs auf InnoDB Tabellen.<br \/>\nAls n\u00e4chstes habe ich ein kleines Shell Script geschrieben welches immer nur eine kleine Anzahl an Datens\u00e4tzen kopiert.<br \/>\n<code>#!\/bin\/bash<\/p>\n<p>backupuser=USERNAME<br \/>\nbackuppassword=PASSWORT<\/p>\n<p>#Findet den letzten Datensatz in der wiederhergestellten Tabelle<br \/>\nBEGIN=`echo \"SELECT messageblk_idnr as ' ' FROM dbmail_messageblks_recover ORDER BY messageblk_idnr DESC limit 1;\" | mysql -u ${backupuser} --password=${backuppassword} -D dbmail -B `<br \/>\nIFS=\"<br \/>\n\"<br \/>\nif [ \"$BEGIN\" = \"\" ];<br \/>\nthen<br \/>\n  BEGIN=0<br \/>\nfi<br \/>\n#Wie viele Datensaetze sollen auf einmal kopiert werden?<br \/>\nINTERVAL=50<br \/>\n#Was die der letzte Datensatz ID<br \/>\nEND=7413082<br \/>\nfor a in `seq $BEGIN $INTERVAL $END`<br \/>\n  do<br \/>\n  echo \"copy ids ${a}\"<br \/>\n  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\n  if [ \"$?\" != \"0\" ];\n  then\n    exit 1\n  fi\ndone\n<\/code><br \/>\nBei diesem Script m\u00fcssen 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 \u00e4ndert den Beginn Wert auf die letzte id+1000 und \u00e4ndert die Zieltabelle in recover2 und lasst das Script nochmal dr\u00fcber laufen. Sollte jetzt bis zum Ende kein Fehler mehr auftreten, kann man davon ausgehen das das Problem zwischen id und id+1000 liegt.<br \/>\nUm jetzt auch noch die letzten funktionsf\u00e4higen Datens\u00e4tze zu bekommen \u00e4ndern 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.<br \/>\nDas ausf\u00fchren wird zu Fehlern f\u00fchren aber mit ein bisserl Gl\u00fcck kann er alle Datens\u00e4tze, bis auf die besch\u00e4digten, kopieren.<br \/>\nAm ende f\u00fcgen 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.<\/p>\n<p>PS: Der Recoverymodus muss wieder ausgeschaltet werden. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dieses Szenario basiert drauf das die Tabelle noch lesbar ist und nur einige Datens\u00e4tze defekt sind.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18,17],"tags":[],"_links":{"self":[{"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/posts\/287"}],"collection":[{"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/comments?post=287"}],"version-history":[{"count":7,"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/posts\/287\/revisions"}],"predecessor-version":[{"id":307,"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/posts\/287\/revisions\/307"}],"wp:attachment":[{"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/media?parent=287"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/categories?post=287"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.schoen-bloed.at\/blog\/wp-json\/wp\/v2\/tags?post=287"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}