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

12

J'ai une table InnoDB que je souhaite modifier. Le tableau compte 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é)?
  • La alter tablesolution "simple" est -elle la plus rapide?

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

Ran
la source
Veuillez SHOW CREATE TABLE tblname\Gindiquer la colonne à modifier, le type de données de la colonne et le nouveau nom de la colonne.
RolandoMySQLDBA
la voici: pastie.org/3078349 la colonne qui doit être renommée est sent_atet pour lui ajouter quelques indices supplémentaires
Ran
sent_at doit être renommé en quoi?
RolandoMySQLDBA
disons: new_sent_at
Ran

Réponses:

14

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 se trouvent é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, 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. Dans ce cas, ce serait 4 000 000.

  • Si COUNT(DISTINCT sent_at)> 4 000 000
    • puis ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Si COUNT(DISTINCT message_id)> 4 000 000
    • puis ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Si COUNT(DISTINCT target_object_id)> 4 000 000
    • puis 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 disposer d'un petit temps d'arrêt afin d'obtenir les 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 basculer sur 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;
RolandoMySQLDBA
la source
12

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-le ALTER TABLEcomme d'habitude, il sera principalement instantané pour les renommages et les suppressions d'index, et assez rapide pour l'ajout d'index (aussi rapide que la lecture de 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 TABLEc'est toujours la plus rapide, mais elle sera probablement horriblement lente car toutes vos données seront réinsérées 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 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.

mezis
la source
Lien vers la documentation du plugin InnoDB (impossible de coller en raison des limites de représentants).
mezis
2
Sur MySQL 5.5, j'ai trouvé RENAME TABLEinstantané (comme prévu) mais un CHANGE COLUMNpour renommer la clé primaire a fait une copie complète ... 7 heures! Peut-être uniquement parce que c'était la clé primaire? Pas bon.
KCD
2

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 du tableau. 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/

Saule
la source
0

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 contenant 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.

TetonSig
la source
1
DDL dans MySQL n'est pas transactionnel. Chaque instruction DDL déclenche un COMMIT. J'ai écrit à ce sujet: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA
0

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é

William Rossier
la source