Mysql : « check extended » pour une table MyIsam

de | 2017-07-21

En informatique comme en cuisine, il est parfois dangereux d’en faire trop.

Pourtant, en cas de problème récalcitrant, il est bon d’avoir dans sa poche de tablier des outils permettant de remettre en marche les récalcitrants.

La Trousse À Outils Du DBA (Prototype)

La Trousse À Outils Du DBA (Prototype)

En nous ne parlons pas uniquement de haches à deux mains, même si leur utilité en cas de crise à fait ses preuves.

Voyons ensemble, sans vous commander, un bête problème MySQL qui nous est arrivé de bon matin, un lundi…

Action !

Le problème

Sur une base MySQL (version 5.1.73. Pas toute fraîche) utilisée par tout le monde mais prise en charge par personne, est arrivée une erreur de corruption de page sur une table centrale. L’alarme tonne comme un concerto de trompettes apocalyptiques. Et quoi de mieux pour l’apocalypse qu’un lundi, à l’aube ?

Un coup d’œil au journal d’évènement du serveur, dont obtenu en shell par :

--log-error=/var/log/mysqld.log

ou depuis MySQL par :

show variables like 'log_error';

… montre une série de lignes de ce format :

170708  8:07:48 [ERROR] /usr/libexec/mysqld: Table './db_name/table_traitresse' is marked as crashed and last (automatic?) repair failed

 

La solution

Pas d’affolement. Les corruptions avec MySQL, ça arrive.

La table en question utilise MyIsam comme moteur de stockage. Or, avec le moteur MyIsam, c’est même étonnant quand ça n’arrive pas. Il écrit en permanence des listes de pointeurs entre clés d’index et données et, lorsqu’il y a beaucoup d’écritures, il se mélange parfois les pinceaux. Le flegme avec lequel les utilisateurs de MySQL prennent ce genre d’événement force le respect. Sur les forums, on sent l’habitude.

Quand c’est possible, on redémarre l’instance en premier pour s’assurer que ce n’est pas juste une corruption en mémoire (ou, diront les pessimistes, pour avoir une chance de descendre la corruption sur disque). Ensuite, serveur arrêté, une vérification avec mysqlcheck suivi d’une correction permet de récupérer la table.

Mais dans notre cas, ce n’est pas possible : l’application est déjà utilisée et seulement une partie en est bloquée.

A chaud, donc, tentons une vérification puis une correction :


MYSQL_ID (MY_LOGIN@localhost) [db_name]> check table table_traitresse;
+-----------------------+-------+----------+-------------------------------------------------------------+
| Table                 | Op    | Msg_type | Msg_text                                                    |
+-----------------------+-------+----------+-------------------------------------------------------------+
| db_name.table_traitresse | check | warning  | Table is marked as crashed and last repair failed           |
| db_name.table_traitresse | check | warning  | Size of indexfile is: 4523008      Should be: 2048          |
| db_name.table_traitresse | check | warning  | Size of datafile is: 4294427512       Should be: 4116606460 |
| db_name.table_traitresse | check | error    | Record-count is not ok; is 442892   Should be: 0            |
| db_name.table_traitresse | check | warning  | Found 176109660 deleted space.   Should be 0                |
| db_name.table_traitresse | check | warning  | Found 20103 deleted blocks       Should be: 0               |
| db_name.table_traitresse | check | warning  | Found 574321 key parts. Should be: 0                        |
| db_name.table_traitresse | check | error    | Corrupt                                                     |
+-----------------------+-------+----------+-------------------------------------------------------------+

MyIsam confirme : il a fait un travail de cochon. Admirez la différence entre les nombres attendus et trouvés.

Tentons de rattraper le coup :


MYSQL_ID (MY_LOGIN@localhost) [db_name]> repair table table_traitresse;
+-----------------------+--------+----------+-----------------------+
| Table                 | Op     | Msg_type | Msg_text              |
+-----------------------+--------+----------+-----------------------+
| db_name.table_traitresse | repair | error    | 127 when fixing table |
| db_name.table_traitresse | repair | status   | Operation failed      |
+-----------------------+--------+----------+-----------------------+

… oups. Ça fonctionne pas.

En consultant la documentation officielle, nous apprenons qu’il existe un paramètre pour la commande « repair ».

C’est quand même pratique, la documentation.


MYSQL_ID (MY_LOGIN@localhost) [db_name]> repair table table_traitresse extended;
+-----------------------+--------+----------+---------------------------------------------------------------------------------+
| Table                 | Op     | Msg_type | Msg_text                                                                        |
+-----------------------+--------+----------+---------------------------------------------------------------------------------+
| db_name.table_traitresse | repair | info     | Key 1 - Found wrong stored record at 4116597352                                 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116602364 |
| db_name.table_traitresse | repair | info     | Found link that points at 4471241259547719200 (outside data file) at 4116602420 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116602500 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116603420 |
| db_name.table_traitresse | repair | info     | Found link that points at 939609790300177748 (outside data file) at 4116603956  |
| db_name.table_traitresse | repair | info     | Found link that points at 5714540334498280037 (outside data file) at 4116603964 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116606196 |
| db_name.table_traitresse | repair | info     | Delete link points outside datafile at 4116620552                               |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116625504 |
| db_name.table_traitresse | repair | info     | Found link that points at 4471241259547719200 (outside data file) at 4116625560 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116625640 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116626560 |
| db_name.table_traitresse | repair | info     | Found link that points at 939609790300177748 (outside data file) at 4116627096  |
| db_name.table_traitresse | repair | info     | Found link that points at 5714540334498280037 (outside data file) at 4116627104 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116629336 |
| db_name.table_traitresse | repair | info     | Found link that points at 7809650167621248101 (outside data file) at 4116630260 |
| db_name.table_traitresse | repair | warning  | Number of rows changed from 2 to 442891                                         |
| db_name.table_traitresse | repair | status   | OK                                                                              |
+-----------------------+--------+----------+---------------------------------------------------------------------------------+

Et voilà. La correction est confirmée :


MYSQL_ID (MY_LOGIN@localhost) [db_name]> check table table_traitresse extended;
+-----------------------+-------+----------+----------+
| Table                 | Op    | Msg_type | Msg_text |
+-----------------------+-------+----------+----------+
| db_name.table_traitresse | check | status   | OK       |
+-----------------------+-------+----------+----------+

Nous pouvons reprendre le cours normal de la journée… C’est à dire, sans doute, d’autres problèmes.

La conclusion

Beaucoup de mots pour pas grand chose, finalement. Pourtant il y a quelque chose à tirer de cette péripétie matinale.

Sans vouloir taper sur MySQL (nous utilisons une version ancienne, un moteur rudimentaire, l’installation a été faites depuis longtemps… Les excuses ne lui manquent pas.), il faut reconnaître que les quelques bases présentent dans notre parc nous donnent plus de mal que les autres.

Corruption d’index et de données, comportement que nous qualifierons pudiquement de « Rock-n-roll » de l’optimizer et une certaine désinvolture dans l’implémentation (nous vous en reparlerons mais il y a des conférences qu’il vaut mieux ne pas regarder pour garder un peu de confiance), ce SGBD nous parait en-deça des autres.

Bien sûr, il existe une grande quantité d’information disponible mais – et c’est un vrai conseil – prenez les suggestions avec beaucoup de recul : au cours de notre recherche, nous avons lu des avis catastrophiques sur ce qui était pourtant un problème bénin. Certains (désactivation de la consistance InnoDB, par exemple, alors que la question portait sur une table en MyIsam comme la notre) vous assurant de longues heures de travail inutile. Genre restauration complète de la base.

Et puis lisez la documentation. Pour MySQL comme partout, ce doit être votre premier réflexe avant de taper une commande nouvelle.

Des problèmes ? des questions ? Exprimez-vous ! Les commentaires sont ouverts. Coquilles et fautes de grammaires sont notre lot quotidien : signalez-les nous à m.capello@dbsqware.com

Crédit photo : saisie d’armes.