MySQL - schnellster Weg zu ALTER TABLE für InnoDB


12

Ich habe eine InnoDB-Tabelle, die ich ändern möchte. Die Tabelle enthält ~ 80 Millionen Zeilen und beendet einige Indizes.

Ich möchte den Namen einer der Spalten ändern und ein paar weitere Indizes hinzufügen.

  • Was ist der schnellste Weg, dies zu tun (vorausgesetzt, ich könnte sogar Ausfallzeiten erleiden - der Server ist ein nicht verwendeter Slave)?
  • Ist eine "Ebene" alter tabledie schnellste Lösung?

Zu diesem Zeitpunkt ist alles, was mich interessiert, Geschwindigkeit :)


Bitte SHOW CREATE TABLE tblname\Gzeigen Sie die Spalte an, die geändert werden muss, den Datentyp der Spalte und den neuen Namen für die Spalte.
RolandoMySQLDBA

hier ist es: pastie.org/3078349 die Spalte, die umbenannt werden muss, ist sent_atund um es ein paar weitere Indizes hinzuzufügen
Ran

sent_at muss in was umbenannt werden?
RolandoMySQLDBA

Sagen wir: new_sent_at
Ran

Antworten:


14

Eine sichere Möglichkeit, eine ALTER TABLE zu beschleunigen, besteht darin, unnötige Indizes zu entfernen

Hier sind die ersten Schritte zum Laden einer neuen Version der Tabelle

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
;

Bitte beachten Sie Folgendes:

  • Ich habe source_persona_index gelöscht, da dies die erste Spalte in 4 anderen Indizes ist

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Ich habe target_persona_index gelöscht, weil es die erste Spalte in 2 anderen Indizes ist

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Ich habe target_persona_relation_type_index gelöscht, da sich die ersten beiden Spalten auch in target_persona_relation_type_message_id_index befinden

OK Das sorgt für unnötige Indizes. Gibt es Indizes mit geringer Kardinalität? Hier ist der Weg, um das festzustellen:

Führen Sie die folgenden Abfragen aus:

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;

Ihrer Frage zufolge gibt es ungefähr 80.000.000 Zeilen. Als Faustregel gilt, dass das MySQL Query Optimizer keinen Index verwendet, wenn die Kardinalität der ausgewählten Spalten größer als 5% der Tabellenzeilenanzahl ist. In diesem Fall wären das 4.000.000.

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

Sobald der Nutzen oder die Nutzlosigkeit dieser Indizes festgestellt wurde, können Sie die Daten neu laden

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

Das war's, richtig? NOPE !!!

Wenn Ihre Website die ganze Zeit aktiv war, werden beim Laden von s_relations_new möglicherweise INSERTs gegen s_relations ausgeführt. Wie können Sie diese fehlenden Zeilen abrufen?

Suchen Sie die maximale ID in s_relations_new und hängen Sie alles nach dieser ID aus s_relations an. Um sicherzustellen, dass die Tabelle eingefroren ist und nur für dieses Update verwendet wird, müssen Sie eine kleine Ausfallzeit haben, um die letzten Zeilen zu erhalten, die in s_relation_new eingefügt wurden. Folgendes tun Sie:

Starten Sie mysql im Betriebssystem neu, damit sich nur root @ localhost anmelden kann (deaktiviert TCP / IP):

$ service mysql restart --skip-networking

Melden Sie sich als Nächstes bei MySQL an und laden Sie die letzten Zeilen:

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;

Starten Sie dann MySQL normal neu

$ service mysql restart

Wenn Sie MySQL nicht herunterfahren können, müssen Sie s_relations ködern und umschalten. Melden Sie sich einfach bei MySQL an und gehen Sie wie folgt vor:

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;

Versuche es !!!

CAVEAT: Sobald Sie mit dieser Operation zufrieden sind, können Sie den alten Tisch so schnell wie möglich löschen:

mysql> DROP TABLE s_relations_old;

12

Die richtige Antwort hängt von der Version der verwendeten MySQL-Engine ab.

Bei Verwendung von 5.6+ werden Umbenennungen und das Hinzufügen / Entfernen von Indizes online durchgeführt , dh ohne alle Daten der Tabelle zu kopieren.

Verwenden Sie ALTER TABLEes einfach wie gewohnt, es ist meistens sofort für Umbenennungen und Indexverluste und relativ schnell für das Hinzufügen von Indizes (so schnell wie das einmalige Lesen der gesamten Tabelle).

Wenn Sie 5.1+ verwenden und das InnoDB-Plugin aktiviert ist, ist das Hinzufügen / Entfernen von Indizes auch online. Ich bin mir nicht sicher über Umbenennungen.

Wenn Sie eine ältere Version verwenden, ALTER TABLEist dies immer noch die schnellste - wird aber wahrscheinlich schrecklich langsam sein, da alle Ihre Daten erneut in eine temporäre Tabelle unter der Haube eingefügt werden.

Endlich Zeit für das Entlarven von Mythen. Leider habe ich hier nicht genug Karma, um Antworten zu kommentieren, aber ich halte es für wichtig, die am häufigsten gewählte Antwort zu korrigieren. Das ist falsch :

Als Faustregel gilt, dass das MySQL Query Optimizer keinen Index verwendet, wenn die Kardinalität der ausgewählten Spalten größer als 5% der Tabellenzeilenanzahl ist

Es ist eigentlich umgekehrt .

Indizes sind nützlich, um wenige Zeilen auszuwählen. Daher ist es wichtig, dass sie eine hohe Kardinalität aufweisen. Dies bedeutet, dass viele unterschiedliche Werte und statistisch wenige Zeilen denselben Wert haben.


Link zur InnoDB-Plugin- Dokumentation (konnte aufgrund von Wiederholungsbeschränkungen nicht eingefügt werden).
Mezis

2
Unter MySQL 5.5 fand ich RENAME TABLEsofort (wie erwartet), aber CHANGE COLUMNum den Primärschlüssel umzubenennen, wurde eine vollständige Kopie erstellt ... 7 Stunden! Möglicherweise nur, weil es der Primärschlüssel war? Nicht gut.
KCD

2

Ich hatte das gleiche Problem mit Maria DB 10.1.12. Nachdem ich die Dokumentation gelesen hatte, stellte ich fest, dass es eine Option gibt, die Operation "an Ort und Stelle" auszuführen, wodurch die Kopie der Tabelle entfernt wird. Mit dieser Option ist die Änderungstabelle sehr schnell. In meinem Fall war es:

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

das ist sehr schnell. Ohne die Algorithmusoption würde es niemals enden.

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


0

Für die Spaltenumbenennung

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

sollte in Ordnung sein und keine Ausfallzeiten mit sich bringen.

Für die Indizes sperrt die Anweisung CREATE INDEX die Tabelle. Wenn es ein unbenutzter Sklave ist, wie Sie erwähnt haben, ist das kein Problem.

Eine andere Möglichkeit wäre, eine brandneue Tabelle mit den richtigen Spaltennamen und Indizes zu erstellen. Dann könnten Sie alle Daten darin kopieren und dann eine Reihe von ausführen

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

Dies würde die Ausfallzeit auf Kosten der vorübergehenden Nutzung des doppelten Speicherplatzes minimieren.


1
DDL in MySQL ist keine Transaktion. Jede DDL-Anweisung löst ein COMMIT aus. Ich schrieb darüber: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA

0

Ich habe auch dieses Problem und habe dieses SQL verwendet:

/*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;   

Ich hoffe es könnte jemandem helfen

Grüße,

Wille

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.