Comment convertir une table de lignes 66 862 521 de MyISAM à InnoDB sans se déconnecter pendant plusieurs heures?

18

est-il possible (et comment) de convertir une énorme table MyISAM en InnoDB sans mettre l'application hors ligne. Il nécessite d'insérer quelques lignes dans ce tableau chaque seconde, mais il est possible de le suspendre pendant environ 2 minutes.

Évidemment ALTER TABLE ... engine = innodb ne fonctionnera pas. Pour cela, j'avais le plan de créer une nouvelle table avec le moteur innodb et d'y copier le contenu. Et à la fin, suspendez le fil du journal des applications et RENAME TABLE.

Malheureusement, même la copie en petits lots de 100 lignes génère un retard important après un certain temps.

Modifier : les lignes existantes ne sont jamais modifiées, ce tableau est utilisé pour la journalisation.

Hendrik Brummermann
la source
3
Eh bien, cette question concerne la réduction du temps de conversation. Je me fiche que les conversations prennent quelques jours ou semaines. Mais il doit fonctionner en arrière-plan sans nécessiter de temps d'arrêt de l'application et sans créer de retard notable.
Hendrik Brummermann

Réponses:

15

Créez une configuration Master-Master comme suit:

  • Créer un deuxième maître, MasterB
  • MasterB agit comme esclave de logTable
  • Créer logTable_newcomme innodb
  • Exécuter INSERT INTO logTable_new SELECT * FROM logTable(psuedocode) sur MasterB, qui envoie la réplication à MasterA
  • Lorsque logTable_newMasterA a terminé la synchronisation, échangez les tables
Derek Downey
la source
10

Compte tenu de la contrainte de:

Je me fiche que les conversations prennent quelques jours ou semaines. Mais il doit fonctionner en arrière-plan sans nécessiter de temps d'arrêt de l'application et sans créer de retard notable

Pendant que vous effectuez la journalisation, si vous avez un bon moyen de définir un marqueur afin que vous puissiez dire à quel moment vous démarrez le processus, vous pouvez ensuite réappliquer les journaux ou les écrire dans un fichier texte afin vous pouvez ensuite les ingérer avec LOAD DATA INFILE

Une partie du problème est que l'écriture en lots plus petits signifie que les index doivent être recalculés encore et encore; vous feriez mieux de tout exécuter en même temps, mais cela pourrait provoquer un certain retard «notable» sur le système .. mais vous n'avez pas à le faire sur votre serveur de production.

  1. Suspendez la journalisation ou définissez un marqueur afin de pouvoir réappliquer les journaux à partir de ce point plus tard.
  2. Copiez votre table MyISM sur un autre système
  3. Sur l'autre système, créez une table InnoDB sous un nom différent et migrez les données (il pourrait même être plus rapide de les vider et de les utiliser LOAD DATA INFILE)
  4. Copiez la table InnoDB sur le système d'origine
  5. Définissez un autre marqueur pour la journalisation.
  6. Réappliquez tous les journaux à la nouvelle table entre les deux derniers marqueurs.
  7. (répétez les étapes 5 et 6 si l'étape # 6 a pris plus d'une minute environ, jusqu'à ce que ce ne soit que quelques secondes)
  8. Échangez les tables (renommez l'ancienne en table_BACKUP, nouvelle sous le nom de l'ancienne)
  9. Rattrapez les journaux depuis le dernier marqueur.
Joe
la source
9

Malheureusement, même la copie en petits lots de 100 lignes génère un retard important après un certain temps.

Ajoutez-vous un délai entre chaque lot, ou mettez-vous simplement par lot les mises à jour et exécutez-vous chaque lot directement après le précédent?

Si c'est le cas, essayez d'écrire la conversion dans votre langue préférée avec quelque chose comme:

repeat
    copy oldest 100 rows that haven't been copied yet to new table
    sleep for as long as that update took
until there are <100 rows unprocessed
stop logging service
move the last few rows
rename tables
restart logging
delete the old table when you are sure the conversion has worked

Cela devrait garantir que la conversion ne prend pas plus de la moitié de la capacité de votre serveur, même en tenant compte des différences de charge imposées car l'utilisation du système varie avec le temps.

Ou si vous souhaitez utiliser autant de temps que possible lorsque le service est relativement inactif, mais que vous devez le désactiver (en faisant potentiellement une pause pendant une période assez longue) lorsque la base de données doit effectuer un certain travail pour ses utilisateurs, remplacez sleep for as long as the update tookpar if the server's load is above <upper measure>, sleep for some seconds then check again, loop around the sleep/check until the load drops below <lower measure>. Cela signifie qu'il peut avancer en temps calme, mais s'arrêtera complètement lorsque le serveur est occupé à exécuter sa charge de travail normale. La détermination de la charge dépendra de votre système d'exploitation - sous Linux et similaire, la valeur moyenne de charge d'une minute /proc/loadavgou la sortie de uptimedevrait faire. <lower measure>et <upper measure>peut avoir la même valeur, bien qu'il soit habituel dans des contrôles comme celui-ci d'avoir une différence de sorte que votre processus ne continue pas à démarrer puis à s'arrêter immédiatement car son propre redémarrage a une influence sur la mesure de charge.

Bien sûr, cela ne fonctionnerait pas pour les tables où les anciennes lignes peuvent être modifiées, mais fonctionnera bien pour une table de journal comme celle que vous décrivez.

Vous voudrez ignorer la sagesse habituelle de créer des index après avoir rempli la nouvelle table dans ce cas. Bien que cela soit en effet plus efficace lorsque vous voulez que les choses soient aussi rapides que possible (l'effet sur le reste du système soit damné), dans ce cas, vous ne voulez pas la grande surabondance de charge à la fin du processus comme le les index sont entièrement créés en une seule fois car il s'agit d'un processus que vous ne pouvez pas interrompre lorsque les choses sont occupées.

David Spillett
la source
4

Quelque chose comme ce travail?

  1. Suspendez la journalisation (pour que la $auto_incrementtable de journalisation mytable ne change pas).
  2. Notez la $auto_incrementvaleur en utilisant SHOW TABLE STATUS LIKE 'mytable'.
  3. CREATE TABLE mytable_new LIKE mytable
  4. ALTER TABLE mytable_new AUTO_INCREMENT=$auto_increment ENGINE=Innodb
  5. RENAME TABLE mytable TO mytable_old, mytable_new TO mytable
  6. Activez à nouveau la journalisation. La table Innodb va maintenant commencer à se remplir.
  7. INSERT INTO mytable SELECT * FROM mytable_old.

Vous pouvez effectuer l'étape 7 par lots ou dans une instruction, car elle ne devrait pas bloquer la journalisation normale.

Riedsio
la source
ce serait toujours bloquant, à cause de la façon dont innodb gère auto_increment ,. par défaut, innodb prend un verrou au niveau de la table lors de l'insertion dans une colonne auto_increment et libère le verrou dès que l'insertion est terminée.
ovais.tariq