MySQL - il modo più veloce per ALTER TABLE per InnoDB


12

Ho una tabella InnoDB che voglio modificare. La tabella ha ~ 80 milioni di righe e chiude alcuni indici.

Voglio cambiare il nome di una delle colonne e aggiungere qualche altro indice.

  • Qual è il modo più veloce per farlo (supponendo che potrei subire anche tempi di inattività - il server è uno slave inutilizzato)?
  • Una "semplice" è alter tablela soluzione più veloce?

In questo momento, tutto ciò che mi interessa è la velocità :)


Per favore SHOW CREATE TABLE tblname\G, mostra la colonna che deve essere cambiata, il tipo di dati della colonna e il nuovo nome per la colonna.
RolandoMySQLDBA il

eccola: pastie.org/3078349 la colonna che deve essere rinominata è sent_ate per aggiungerla ad alcuni altri indici
Funzionò il

sent_at deve essere rinominato in cosa?
RolandoMySQLDBA,

diciamo: new_sent_at
Ran

Risposte:


14

Un modo sicuro per accelerare una tabella ALTER è rimuovere gli indici non necessari

Ecco i passaggi iniziali per caricare una nuova versione della tabella

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
;

Si prega di notare quanto segue:

  • Ho lasciato source_persona_index perché è la prima colonna in altri 4 indici

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Ho eliminato target_persona_index perché è la prima colonna in altri 2 indici

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Ho eliminato target_persona_relation_type_index perché le prime 2 colonne sono anche in target_persona_relation_type_message_id_index

OK Questo si occupa di indici non necessari. Esistono indici con cardinalità bassa? Ecco il modo per determinare che:

Esegui le seguenti query:

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;

Secondo la tua domanda, ci sono circa 80.000.000 di righe. Come regola generale, lo Strumento per ottimizzare le query MySQL non utilizzerà un indice se la cardinalità delle colonne selezionate è maggiore del 5% del conteggio delle righe della tabella. In questo caso, sarebbe 4.000.000.

  • Se COUNT(DISTINCT sent_at)> 4.000.000
    • poi ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Se COUNT(DISTINCT message_id)> 4.000.000
    • poi ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Se COUNT(DISTINCT target_object_id)> 4.000.000
    • poi ALTER TABLE s_relations_new DROP INDEX target_object_index;

Una volta determinata l'utilità o l'inutilità di tali indici, è possibile ricaricare i dati

#
# 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;

Esatto, vero? NO !!!

Se il tuo sito Web è stato attivo per tutto questo tempo, potrebbero esserci INSERT in esecuzione contro s_relations durante il caricamento di s_relations_new. Come puoi recuperare quelle righe mancanti?

Vai a trovare l'id massimo in s_relations_new e aggiungi tutto dopo quell'ID da s_relations. Per garantire che la tabella sia congelata e utilizzata solo per questo aggiornamento, è necessario avere un po 'di tempo di inattività per ottenere quelle ultime righe che sono state inserite in s_relation_new. Ecco cosa fai:

Nel sistema operativo, riavviare mysql in modo che nessun altro possa accedere tranne root @ localhost (disabilita TCP / IP):

$ service mysql restart --skip-networking

Quindi, accedi a mysql e carica le ultime righe:

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;

Quindi, riavvia mysql normalmente

$ service mysql restart

Ora, se non riesci a smontare mysql, dovrai fare un esca-e-cambiare su s_relations. Accedi a mysql e procedi come segue:

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;

Provaci !!!

CAVEAT: una volta che sei soddisfatto di questa operazione, puoi lasciare il vecchio tavolo al più presto:

mysql> DROP TABLE s_relations_old;

12

La risposta corretta dipende dalla versione del motore MySQL che stai utilizzando.

Se si utilizza 5.6+, i nomi e l'aggiunta / rimozione di indici vengono eseguiti online , ovvero senza copiare tutti i dati della tabella.

Basta usare ALTER TABLEcome al solito, sarà per lo più istantaneo per rinominare e rilasciare gli indici e ragionevolmente veloce per l'aggiunta dell'indice (veloce come leggere tutta la tabella una volta).

Se si utilizza 5.1+ e il plug-in InnoDB è abilitato, anche l'aggiunta / rimozione di indici sarà online. Non sono sicuro di rinominare.

Se si utilizza la versione precedente, ALTER TABLEè ancora il più veloce, ma probabilmente sarà terribilmente lento perché tutti i dati verranno reinseriti in una tabella temporanea sotto il cofano.

Finalmente, è tempo di sfatare il mito. Sfortunatamente non ho abbastanza karma qui per commentare le risposte, ma ritengo sia importante correggere la risposta più votata. Questo è sbagliato :

Come regola generale, lo Strumento per ottimizzare le query MySQL non utilizzerà un indice se la cardinalità delle colonne selezionate è maggiore del 5% del conteggio delle righe della tabella

In realtà è il contrario .

Gli indici sono utili per selezionare poche righe, quindi è importante che abbiano una cardinalità elevata , il che significa molti valori distinti e statisticamente poche righe con lo stesso valore.


Link alla documentazione del plug-in InnoDB (impossibile incollare a causa dei limiti di reputazione).
Mezis,

2
Su MySQL 5.5 ho trovato RENAME TABLEistantaneo (come previsto) ma CHANGE COLUMNa rinominare la chiave primaria ha fatto una copia completa ... 7 ore! Forse solo perché era la chiave primaria? Non bene.
KCD,

2

Ho avuto lo stesso problema con Maria DB 10.1.12, quindi dopo aver letto la documentazione ho scoperto che esiste un'opzione per eseguire l'operazione "sul posto" che elimina la copia della tabella. Con questa opzione la tabella di modifica è molto veloce. Nel mio caso era:

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

questo è molto veloce. Senza l'opzione dell'algoritmo non sarebbe mai terminato.

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


0

Per la colonna rinomina,

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

dovrebbe andare bene e non portare alcun tempo morto.

Per gli indici, l'istruzione CREATE INDEX bloccherà la tabella. Se è uno schiavo inutilizzato come hai detto, non è un problema.

Un'altra opzione sarebbe quella di creare una nuova tabella con i nomi e gli indici delle colonne corretti. Quindi è possibile copiare tutti i dati al suo interno, quindi eseguire una serie di

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

Ciò ridurrebbe al minimo i tempi di fermo a scapito dell'utilizzo temporaneo del doppio dello spazio.


1
DDL in MySQL non è transazionale. Ogni istruzione DDL attiva un COMMIT. Ho scritto su questo: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA

0

Ho anche questo problema e ho usato questo 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;   

Spero che possa aiutare qualcuno

Saluti,

Volere

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.