MySQL - cara tercepat untuk ALTER TABLE untuk InnoDB

12

Saya memiliki tabel InnoDB yang ingin saya ubah. Tabel ini memiliki ~ 80 juta baris, dan keluar dari beberapa indeks.

Saya ingin mengubah nama salah satu kolom dan menambahkan beberapa indeks lagi.

  • Apa cara tercepat untuk melakukannya (dengan asumsi saya bisa menderita downtime bahkan - server adalah budak yang tidak digunakan)?
  • Apakah "polos" alter table, solusi tercepat?

Saat ini, yang saya pedulikan hanyalah kecepatan :)

Ran
sumber
Tolong SHOW CREATE TABLE tblname\G, perlihatkan kolom yang perlu diubah, tipe data dari kolom, dan nama baru untuk kolom.
RolandoMySQLDBA
ini dia: pastie.org/3078349 kolom yang perlu diganti namanya adalah sent_atdan menambahkannya beberapa indeks lagi
Ran
sent_at perlu diganti namanya menjadi apa?
RolandoMySQLDBA
katakanlah: new_sent_at
Ran

Jawaban:

14

Salah satu cara pasti untuk mempercepat ALTER TABLE adalah dengan menghapus indeks yang tidak perlu

Berikut adalah langkah-langkah awal untuk memuat versi baru tabel

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
;

Harap perhatikan hal berikut:

  • Saya menjatuhkan source_persona_index karena itu adalah kolom pertama dalam 4 indeks lainnya

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Saya menjatuhkan target_persona_index karena ini adalah kolom pertama dalam 2 indeks lainnya

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Saya menjatuhkan target_persona_relation_type_index karena 2 kolom pertama juga di target_persona_relation_type_message_id_index

OK Itu menangani indeks yang tidak perlu. Apakah ada indeks yang memiliki kardinalitas rendah? Inilah cara untuk menentukan itu:

Jalankan pertanyaan berikut:

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;

Menurut pertanyaan Anda, ada sekitar 80.000.000 baris. Sebagai aturan praktis, Pengoptimal Permintaan MySQL tidak akan menggunakan indeks jika kardinalitas kolom yang dipilih lebih besar dari 5% dari jumlah baris tabel. Dalam hal ini, itu akan menjadi 4.000.000.

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

Setelah kegunaan atau kegunaan dari indeks tersebut telah ditentukan, Anda dapat memuat ulang data

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

Itu dia kan? TIDAK !!!

Jika situs web Anda telah menyala sepanjang waktu ini, mungkin ada INSERT yang berjalan terhadap s_relations selama pemuatan s_relations_new. Bagaimana Anda bisa mendapatkan kembali baris yang hilang itu?

Pergi temukan id maksimum di s_relations_new dan tambahkan semuanya setelah ID itu dari s_relations. Untuk memastikan bahwa tabel dibekukan dan hanya digunakan untuk pembaruan ini, Anda harus memiliki sedikit downtime demi mendapatkan baris terakhir yang dimasukkan ke s_relation_new. Inilah yang Anda lakukan:

Di OS, mulai ulang mysql sehingga tidak ada orang lain yang bisa masuk tetapi root @ localhost (menonaktifkan TCP / IP):

$ service mysql restart --skip-networking

Selanjutnya, masuk ke mysql dan muat baris terakhir itu:

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;

Kemudian, restart mysql secara normal

$ service mysql restart

Sekarang, jika Anda tidak dapat menghapus mysql, Anda harus melakukan umpan-dan-aktifkan pada s_relations. Cukup login ke mysql dan lakukan hal berikut:

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;

Cobalah !!!

CAVEAT: Setelah Anda puas dengan operasi ini, Anda dapat menjatuhkan meja lama sesegera mungkin:

mysql> DROP TABLE s_relations_old;
RolandoMySQLDBA
sumber
12

Jawaban yang benar tergantung pada versi mesin MySQL yang Anda gunakan.

Jika menggunakan 5.6+, penggantian nama dan penambahan / penghapusan indeks dilakukan secara online , yaitu tanpa menyalin semua data tabel.

Cukup gunakan ALTER TABLEseperti biasa, sebagian besar instan untuk penggantian nama dan penurunan indeks, dan cukup cepat untuk penambahan indeks (secepat membaca semua tabel sekali).

Jika menggunakan 5.1+, dan plugin InnoDB diaktifkan, menambahkan / menghapus indeks akan online juga. Tidak yakin tentang penggantian nama.

Jika menggunakan versi yang lebih lama, ALTER TABLEmasih yang tercepat — tetapi mungkin akan sangat lambat karena semua data Anda akan dimasukkan kembali ke tabel sementara di bawah tenda.

Akhirnya, waktu untuk menghilangkan prasangka mitos. Sayangnya saya tidak memiliki cukup karma di sini untuk mengomentari jawaban, tetapi saya merasa penting untuk memperbaiki jawaban yang paling banyak dipilih. Ini salah :

Sebagai aturan praktis, Pengoptimal Permintaan MySQL tidak akan menggunakan indeks jika kardinalitas kolom yang dipilih lebih besar dari 5% dari jumlah baris tabel

Sebenarnya sebaliknya .

Indeks berguna untuk memilih beberapa baris, jadi penting mereka memiliki kardinalitas tinggi , yang berarti banyak nilai berbeda dan secara statistik sedikit baris dengan nilai yang sama.

mezis
sumber
Tautan ke dokumentasi plugin InnoDB (tidak dapat menempel karena batas rep).
mezis
2
Pada MySQL 5.5 saya menemukan RENAME TABLEinstan (seperti yang diharapkan) tetapi CHANGE COLUMNuntuk mengganti nama kunci utama melakukan salinan lengkap ... 7 jam! Mungkin hanya karena itu adalah kunci utama? Tidak baik.
KCD
2

Saya memiliki masalah yang sama dengan Maria DB 10.1.12, kemudian setelah membaca dokumentasi saya menemukan bahwa ada opsi untuk melakukan operasi "di tempat" yang menghilangkan salinan tabel. Dengan opsi ini, tabel alter sangat cepat. Dalam kasus saya itu adalah:

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

ini sangat cepat. Tanpa opsi algoritma itu tidak akan pernah berakhir.

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

Saule
sumber
0

Untuk mengganti nama kolom,

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

harus baik dan tidak membawa downtime.

Untuk indeks, pernyataan CREATE INDEX akan mengunci tabel. Jika itu adalah budak yang tidak digunakan seperti yang Anda sebutkan, itu bukan masalah.

Satu opsi lainnya adalah membuat tabel baru yang memiliki nama dan indeks kolom yang tepat. Kemudian Anda bisa menyalin semua data ke dalamnya, lalu jalankan serangkaian

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

Ini akan meminimalkan waktu henti dengan biaya sementara menggunakan ruang dua kali lipat.

TetonSig
sumber
1
DDL di MySQL bukan transaksional. Setiap pernyataan DDL memicu KOMIT. Saya menulis tentang ini: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA
0

Saya punya masalah ini juga dan saya menggunakan SQL ini:

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

Saya harap ini bisa membantu seseorang

Salam,

Akan

William Rossier
sumber