it-swarm.it

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

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)?
  • È un "semplice" alter table, la soluzione più veloce?

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

12
Ran

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 eliminato 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
    • quindi ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Se COUNT(DISTINCT message_id)> 4.000.000
    • quindi ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Se COUNT(DISTINCT target_object_id)> 4.000.000
    • quindi 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 assicurare 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 se non 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;
14
RolandoMySQLDBA

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

Se si utilizza 5.6+, vengono rinominati e aggiunti/rimossi gli indici online , cioè senza copiare tutti i dati della tabella.

Usa ALTER TABLE come al solito, sarà per lo più istantaneo per rinominazioni e cadute dell'indice 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 tuoi 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 viceversa .

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

12
mezis

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/

2
Saule

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 inattività a costo di utilizzare temporaneamente il doppio dello spazio.

0
TetonSig

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 possa aiutare qualcuno

Saluti,

Volere

0
William Rossier