Recuperare un database MySQL/MariaDB corrotto utilizzando i data files (InnoDB)
❓ Cause
Un database MySQL corrotto può dipendere da molteplici cause, la maggior parte delle volte è possibile risolvere con gli strumenti integrati di repair.
Mi è capitato però di ritrovarmi dei database corrotti a causa di immagini Docker MySQL o MariaDB con versioni non pinnate.
                Vorrei ricordare che utilizzare :latest in un'immagine docker è un'attività terroristica, specialmente per i database.
Le immagini Docker ufficiali spesso offrono un percorso di aggiornamento ma non garantiscono il funzionamento saltando più di una versione.
✔️ Prerequisiti
- Linux con Docker funzionante
- Una minima conoscenza di docker
- Una directory di lavoro
- La directory /var/lib/mysql corrotta del database che ci interessa recuperare (Io ho provato solo InnoDB), in questo caso è il database di BookStack
- 
                        I comandi MySQL con -prichiedono di inserire ogni volta la password ma se è vuota basta premere invio, in alternativa omettere il-p
🏁 Si parte
Utilizzeremo delle utilities di MySQL (archiviate) per ottenere il DDL del database, questo passo abbastanza rognoso è possibile saltarlo se si ha un vecchio dump da cui estrarre lo schema.
Purtroppo questi tool non funzionano correttamente con MariaDB e le versioni di MySQL maggiori della 5, quindi utilizzeremo l'ultima immagine MySQL 5 disponibile (al momento 5.7).
                    Copiamo la directory mysql
                     danneggiata nella vostra directory di
                        lavoro.
                
I files devono avere uid e gid dell'utente mysql dell'immagine docker (999), quindi:
sudo chown -R 999:999 mysqlFacciamo partire un container (effimero va bene):
docker run --name mysql5 --rm -it \--mount type=bind,src=$(pwd)/mysql,dst=/var/lib/mysql \mysql:5.7
                    
                        💥
                        Boooooom!!
                    
                    
                        💥
                
            
beh se funzionasse non sareste qui vero?
Ritentiamo con una shell:
docker run --name mysql5 --rm -it \
--entrypoint=/bin/bash \
--mount type=bind,src=$(pwd)/mysql,dst=/var/lib/mysql \
mysql:5.7
Per far partire manualmente MySQL utilizziamo il comando:
gosu mysql mysqld
                    gosu
                     è una sorta di su  scritto in go che ho trovato all'interno dell'immagine docker di MySQL, è comodo perché non occorre quotare gli argomenti e usare -c
                    , d'altra parte l'entrypoint dell'immagine lo
                        usa per lanciare il server, chi sono io per non essere d'accordo (cit.)?
                
Ovviamente l'output è lo stesso...avevate dubbi?
🏃♂️ Soluzione veloce
Se siamo fortunati questa soluzione può andar bene, ma potrebbe non avere tutti i record aggiornati.
                    Per prima cosa bisogna eliminare i files 
                    ib_logfile0
                     e ib_logfile1
                    , 
                    nella cartella
                     /var/lib/mysql
                     (tanto avete una copia no?), ora facciamo
                        partire MySQL (gosu mysql mysqld
                    ) e, per la serie moriremo di log, un
                        miliardo di questi:
                
ma oggi mi sentivo fortunato 🍀 ... quasi affogando nello spam ho trovato un'informazione interessante:
Questo significa che la versione dei data files è inferiore a quella della nostra immagine (5.7), l'avevo detto che oggi mi sentivo fortunato...
apriamo un'altra shell ed entriamo nel container
- docker exec -it mysql5 /bin/bash
- mysql_upgrade -p
Continuiamo a sentirci fortunati? Facciamo un dump
mysqldump -p bookstack > /tmp/bookstack.sql
                    controlliamo il file con more /tmp/bookstack.sql
                     (potete installare less o vim con il comando
                        apt), se è di nostro gradimento possiamo recuperare il file dall'host con:
                
docker cp mysql5:/tmp/bookstack.sql 🤔 Ok, ma io ho dimenticato la password di MySQL
Oppure non l'ho mai saputa ed essendo le 3 di notte non posso telefonare al proprietario del database per farmela dare (ad essere sinceri probabilmente non la conosce nemmeno).
                    Cosa si fa? Spegniamo il container (o
                        dall'altra shell killall mysqld
                    )
                
Torniamo alla shell principale e creiamo un init file per MySQL:
echo "ALTER USER 'root'@'localhost' IDENTIFIED BY '';" > /tmp/init-file.txtFacciamo partire MySQL con il comando:
gosu mysql mysqld --init-file=/tmp/init-file.txtTorniamo alla seconda shell e questa volta non abbiamo bisogno di password.
🥱 Se oggi non mi sento fortunato
Mi è capitato un altro database MySQL che non riconosceva nessuna tabella del database in questione, quindi anche riparando le tabelle avevano i data files sul filesystem ma MySQL asseriva di non trovarne nessuna.
                    Se avete già provato la soluzione veloce e
                        state leggendo questo paragrafo, spegnete tutti i container, cancellate la directory mysql e
                        ricopiate quella originale, non dimenticate chown
                    .
                
Ovviamente si inizia con una shell:
docker run --name mysql5 --rm -it \--entrypoint=/bin/bash \--mount type=bind,src=$(pwd)/mysql,dst=/var/lib/mysql \mysql:5.7
Copiamo dall'host l'archivio con le utilities:
docker cp mysql-utilities-1.6.5.tar.gz mysql5:/tmp/Torniamo al container e scompattiamo l'archivio:
- cd /tmp/
- tar xvf mysql-utilities-1.6.5.tar.gz
                    Ora ci serve Python
                    , quindi:
                
apt update && apt install -y pythonproseguiamo:
- cd /tmp/mysql-utilities-1.6.5
- python setup.py install
vediamo cosa possiamo fare:
esiste una modalità diagnostica dello script che tira fuori qualcosa, ma spesso salta tabelle e non estrae informazioni essenziali, quindi bisogna che il server sia su, cosa al momento non possibile finché non elimino i Binlog.
Nell'esame fatto in precedenza sembra che i Binlog siano vuoti quindi procediamo alla potatura (a MySQL spento, comunque non partiva, ma è sempre meglio essere attenti).
                    Rechiamoci nella cartella /var/lib/mysql
                     ed eliminiamo i files ib_logfile0
                     e ib_logfile1
                
Ora facciamo ripartire MySQL:gosu mysql mysqld
Apriamo una seconda shell sul container:
docker exec -it mysql5 /bin/bash
                    per prima cosa dobbiamo abilitare l'accesso
                        root a 127.0.0.1
                    , non so per quale motivo le utilities usino
                        il numerico:
                
                    avviamo MySQL cl (mysql mysql
                    )
                
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '';
FLUSH PRIVILEGES
            Verifichiamo che funzioni con:
mysql -h 127.0.0.1 mysqlOra ritentiamo "forte":
mysqlfrm \--server=root@localhost--port 3307 \--user mysql \/var/lib/mysql/bookstack | grep -Ev '^(#|ERROR:)' > /tmp/bookstack-ddl.sql
La porta 3307 è per la seconda istanza di MySQL che tira su.
Ora sembra ci sia tutto (controllate con grep -F 'CREATE TABLE' bookstack-ddl.sql)
                Torniamo all'host e recuperiamo il file:
docker cp mysql5:/tmp/bookstack-ddl.sql .
                    Il nostro amato tool non inserisce i 
                    ;
                     alla fine degli statement CREATE TABLE
                    , ora potreste farvelo a mano con un editor,
                        ma se guardiamo com'è fatta l'ultima linea della CREATE TABLE
                     possiamo usare sed:
                
CREATE TABLE `bookstack`.`activities` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            sed -i -e 's/COLLATE=utf8mb4_unicode_ci$/COLLATE=utf8mb4_unicode_ci;/g' bookstack-ddl.sqlSpegniamo i due container e facciamone un altro, questa volta senza passargli il database, il container ne creerà uno vuoto:
docker run --name mysql5 --rm -it \-e MYSQL_ALLOW_EMPTY_PASSWORD=yes \mysql:5.7
Copiamolo il DDL nel nuovo container:
docker cp bookstack-ddl.sql mysql5:/tmp/apriamo una seconda shell nel container:
docker exec -it mysql5 /bin/bashcreiamo e importiamo lo schema del database:
- mysqladmin create bookstack
- mysql bookstack < /tmp/bookstack-ddl.sql
mysqladmin drop bookstack) e riprovate, non posso
                    mica fare tutto io.🤹 I giocolieri
Questa è la parte più "maranza" del recupero:
🪄 [ Magia 1 ]
(
    for table in $(mysql -BN -e 'show tables from bookstack;'); do \
        echo "ALTER TABLE $table DISCARD TABLESPACE;"; \
    done
) | mysql bookstack
            
                    Torniamo all'host entriamo nella cartella mysql corrotta, ora nella sottocartella bookstack e copiamo i files *.frm nel
                        container:
for idb in *.ibd; do \
    docker cp $idb mysql5:/var/lib/mysql/bookstack/; \
done
            Torniamo alla shell secondaria nel container e sistemiamo l'ownership dei files:
chown -R mysql:mysql /var/lib/mysql/bookstack
                    
                    
                
🪄 [ Magia 2 ]
(
    for table in $(mysql -BN -e 'show tables from bookstack;'); do \
        echo "ALTER TABLE $table IMPORT TABLESPACE;"; \
    done
) | mysql bookstack
            Ora possiamo effettuare il dump:
mysqldump -p bookstack > /tmp/bookstack.sql
                    controlliamo il file con more /tmp/bookstack.sql
                     (potete installare less o vim con il comando
                        apt), se è di nostro gradimento possiamo recuperare il file dall'host con:
                
docker cp mysql5:/tmp/bookstack.sql .🔚 Conclusioni
                    Confrontando i due dump, con il secondo metodo ho perso delle CONSTRAINT
                     sulle FOREIGN KEY
                     ed una tabella, quindi occorre sempre ricontrollare lo schema, in questo caso il metodo veloce ha funzionato, in un altro recupero invece il database non vedeva proprio le tabelle, quindi è stato il meglio che si poteva fare. 
                
Probabilmente si perdono anche gli indici, in BookStack non ne ho trovati e tipicamente le applicazioni che usano PHP/MySQL non ne usano.
                    Pensavo mysql_upgrade
                     non servisse in realtà mi sono accorto che
                        ha "resuscitato" la tabella joint_permissions
                     che invece non risulta senza fare l'upgrade
                        e nella modalità "disperato".
                
                    Insomma fate varie prove e tenete sempre da
                        parte la copia originale della cartella
                     /var/lib/mysql
                     corrotta.
                
PS : Non sono amico dei database e quando proprio devo uso PostgreSQL!
Recuperare un database MySQL corrotto