web-dev-qa-db-fra.com

MySQL - le moyen le plus rapide pour ALTER TABLE pour InnoDB

J'ai une table InnoDB que je souhaite modifier. La table a environ 80 millions de lignes et quitte quelques indices.

Je veux changer le nom d'une des colonnes et ajouter quelques indices supplémentaires.

  • Quel est le moyen le plus rapide de le faire (en supposant que je pourrais subir même des temps d'arrêt - le serveur est un esclave inutilisé)?
  • Est un "simple" alter table, la solution la plus rapide?

En ce moment, tout ce qui m'importe c'est la vitesse :)

12
Ran

Un moyen sûr d'accélérer une ALTER TABLE est de supprimer les index inutiles

Voici les étapes initiales pour charger une nouvelle version du tableau

CREATE TABLE s_relations_new LIKE s_relations;
#
# Drop Duplicate Indexes
#
ALTER TABLE s_relations_new
    DROP INDEX source_persona_index,
    DROP INDEX target_persona_index,
    DROP INDEX target_persona_relation_type_index
;

Veuillez noter ce qui suit:

  • J'ai laissé tomber source_persona_index car c'est la première colonne de 4 autres index

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • J'ai supprimé target_persona_index car c'est la première colonne de 2 autres index

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • J'ai supprimé target_persona_relation_type_index car les 2 premières colonnes sont également dans target_persona_relation_type_message_id_index

OK Cela prend en charge les index inutiles. Y a-t-il des index à faible cardinalité? Voici la façon de déterminer que:

Exécutez les requêtes suivantes:

SELECT COUNT(DISTINCT sent_at)               FROM s_relations;
SELECT COUNT(DISTINCT message_id)            FROM s_relations;
SELECT COUNT(DISTINCT target_object_id)      FROM s_relations;

Selon votre question, il y a environ 80 000 000 lignes. En règle générale, l'optimiseur de requêtes MySQL n'utilisera pas d'index si la cardinalité des colonnes sélectionnées est supérieure à 5% du nombre de lignes de la table. Dans ce cas, ce serait 4 000 000.

  • Si COUNT(DISTINCT sent_at)> 4 000 000
    • alors ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Si COUNT(DISTINCT message_id)> 4 000 000
    • alors ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Si COUNT(DISTINCT target_object_id)> 4 000 000
    • alors ALTER TABLE s_relations_new DROP INDEX target_object_index;

Une fois l'utilité ou l'inutilité de ces index déterminées, vous pouvez recharger les données

#
# Change the Column Name
# Load the Table
#
ALTER TABLE s_relations_new CHANGE sent_at sent_at_new int(11) DEFAULT NULL;
INSERT INTO s_relations_new SELECT * FROM s_relations;

C'est ça, non? NAN !!!

Si votre site Web a été mis en place pendant tout ce temps, des INSERT peuvent s'exécuter sur s_relations lors du chargement de s_relations_new. Comment pouvez-vous récupérer ces lignes manquantes?

Allez trouver l'ID maximum dans s_relations_new et ajoutez tout après cet ID dans s_relations. Pour vous assurer que la table est figée et utilisée uniquement pour cette mise à jour, vous devez avoir un petit temps d'arrêt pour obtenir ces dernières lignes insérées dans s_relation_new. Voici ce que vous faites:

Dans l'OS, redémarrez mysql pour que personne d'autre ne puisse se connecter mais root @ localhost (désactive TCP/IP):

$ service mysql restart --skip-networking

Ensuite, connectez-vous à mysql et chargez ces dernières lignes:

mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

Ensuite, redémarrez mysql normalement

$ service mysql restart

Maintenant, si vous ne pouvez pas supprimer mysql, vous devrez faire un appât et activer les s_relations. Connectez-vous simplement à mysql et procédez comme suit:

mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations_old WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

Essaie !!!

CAVEAT: Une fois que vous êtes satisfait de cette opération, vous pouvez déposer l'ancienne table à votre convenance:

mysql> DROP TABLE s_relations_old;
14
RolandoMySQLDBA

La bonne réponse dépend de la version du moteur MySQL que vous utilisez.

Si vous utilisez 5.6+, les renommages et l'ajout/suppression d'index sont effectués en ligne , c'est-à-dire sans copier toutes les données de la table.

Utilisez simplement ALTER TABLE comme d'habitude, il sera principalement instantané pour les renommages et les suppressions d'index, et raisonnablement rapide pour l'ajout d'index (aussi rapide que de lire toute la table une fois).

Si vous utilisez 5.1+ et que le plugin InnoDB est activé, l'ajout/la suppression d'index sera également en ligne. Pas sûr de renommer.

Si vous utilisez une ancienne version, ALTER TABLE est toujours le plus rapide, mais sera probablement horriblement lent car toutes vos données sera réinséré dans une table temporaire sous le capot.

Enfin, il est temps de démystifier le mythe. Malheureusement, je n'ai pas assez de karma ici pour commenter les réponses, mais je pense qu'il est important de corriger la réponse la plus votée. C'est faux :

En règle générale, MySQL Query Optimizer n'utilisera pas d'index si la cardinalité des colonnes sélectionnées est supérieure à 5% du nombre de lignes de table

C'est en fait l'inverse .

Les indices sont utiles pour sélectionner quelques-uns lignes, il est donc important qu'ils aient une cardinalité élevée, ce qui signifie de nombreuses valeurs distinctes et statistiquement peu de lignes avec la même valeur.

12
mezis

J'ai eu le même problème avec Maria DB 10.1.12, puis après avoir lu la documentation, j'ai trouvé qu'il y avait une option pour effectuer l'opération "sur place" ce qui élimine la copie de la table. Avec cette option, la table alter est très rapide. Dans mon cas, c'était:

alter table user add column (resettoken varchar(256),
  resettoken_date date, resettoken_count int), algorithm=inplace;

c'est très rapide. Sans l'option d'algorithme, il ne se terminerait jamais.

https://mariadb.com/kb/en/mariadb/alter-table/

2
Saule

Pour la colonne renommer,

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

devrait être bien et ne porter aucun temps d'arrêt.

Pour les index, l'instruction CREATE INDEX verrouille la table. Si c'est un esclave inutilisé comme vous l'avez mentionné, ce n'est pas un problème.

Une autre option serait de créer une toute nouvelle table avec les noms de colonne et les index appropriés. Ensuite, vous pouvez y copier toutes les données, puis exécuter une série de

BEGIN TRAN;
ALTER TABLE RENAME tablename tablenameold;
ALTER TABLE RENAME newtablename tablename;
DROP TABLE tablenameold;
COMMIT TRAN;

Cela minimiserait les temps d'arrêt au prix d'une utilisation temporaire de deux fois l'espace.

0
TetonSig

J'ai aussi ce problème et j'ai utilisé ce SQL:

/*on créé la table COPY SANS les nouveaux champs et SANS les FKs */
CREATE TABLE IF NOT EXISTS prestations_copy LIKE prestations;

/* on supprime les FKs de la table actuelle */
ALTER TABLE `prestations`
DROP FOREIGN KEY `fk_prestations_pres_promos`,
DROP FOREIGN KEY `fk_prestations_activites`;

/* on remet les FKs sur la table copy */
ALTER TABLE prestations_copy 
    ADD CONSTRAINT `fk_prestations_activites` FOREIGN KEY (`act_id`) REFERENCES `activites` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
    ADD CONSTRAINT `fk_prestations_pres_promos` FOREIGN KEY (`presp_id`) REFERENCES `pres_promos` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION;

/* On fait le transfert des données de la table actuelle vers la copy, ATTENTION: il faut le même nombre de colonnes */
INSERT INTO prestations_copy
SELECT * FROM prestations;

/* On modifie notre table copy de la façon que l'on souhaite */
ALTER TABLE `prestations_copy`
    ADD COLUMN `seo_mot_clef` VARCHAR(50) NULL;

/* on supprime la table actuelle et renome la copy avec le bon nom de table */
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE prestations;
RENAME TABLE prestations_copy TO prestations;
SET FOREIGN_KEY_CHECKS=1;   

J'espère que ça pourrait aider quelqu'un

Cordialement,

Volonté

0
William Rossier