ALTER TABLE pada tabel besar dengan kolom yang diindeks

14

Saya memiliki tabel besar dengan kolom VARCHAR (20), dan saya perlu mengubahnya menjadi kolom VARCHAR (50). Biasanya, melakukan ALTER TABLE (menambahkan TINYINT) pada tabel khusus ini membutuhkan waktu sekitar 90-120 menit untuk diselesaikan, jadi saya benar-benar hanya dapat melakukannya pada Sabtu atau Minggu malam untuk menghindari memengaruhi pengguna database. Jika memungkinkan saya ingin melakukan modifikasi ini sebelum itu.

Kolom juga diindeks, yang saya anggap akan membuat ALTER TABLE lebih lambat, karena harus membangun kembali indeks setelah memodifikasi panjang kolom.

Aplikasi web diatur dalam lingkungan replikasi MySQL (26 budak dan satu master). Saya ingat sekali membaca di suatu tempat bahwa salah satu metode adalah pertama-tama melakukan ALTER TABLE pada setiap budak (meminimalkan dampak pada pengguna), kemudian lakukan ini pada Master, tetapi bukankah itu kemudian mencoba untuk mereplikasi perintah ALTER TABLE ke budak?

Jadi pertanyaan saya adalah: apa cara terbaik bagi saya untuk memodifikasi tabel ini dengan gangguan minimum bagi pengguna saya?

Sunting: tabelnya adalah InnoDB.

Matt Healy
sumber
menambahkan kolom tinyint berarti benar-benar menambahkan kolom dengan nilai default? Karena melakukan itu di atas meja besar bisa memakan waktu lama ..
Marian

Jawaban:

13

Jika Anda sedikit suka berpetualang, Anda bisa membawa masalah ke tangan Anda dengan melakukan ALTER TABLE dalam tahapan yang bisa Anda lihat. Misalkan tabel yang ingin Anda ubah disebut WorkingTable. Anda bisa melakukan perubahan secara bertahap seperti ini:

#
#  Script 1
#  Alter table structure of a single column of a large table
#
CREATE TABLE WorkingTableNew LIKE WorkingTable;
ALTER TABLE WorkingTableNew MODIFY BigColumn VARCHAR(50);
INSERT INTO WorkingTableNew SELECT * FROM WorkingTable;
ALTER TABLE WorkingTable RENAME WorkingTableOld;
ALTER TABLE WorkingTableNew RENAME WorkingTable;
DROP TABLE WorkingTableOld;

Anda dapat melakukan ini pada semua budak. Bagaimana dengan tuannya ??? Bagaimana Anda mencegah ini dari replikasi ke budak. Sederhana: Jangan mengirim SQL ke log biner master. Cukup matikan biner logging di sesi sebelum melakukan hal-hal ALTER TABLE:

#
#  Script 2
#  Alter table structure of a single column of a large table
#  while preventing it from replicating to slaves
#
SET SQL_LOG_BIN = 0;
CREATE TABLE WorkingTableNew LIKE WorkingTable;
ALTER TABLE WorkingTableNew MODIFY BigColumn VARCHAR(50);
INSERT INTO WorkingTableNew SELECT SQL_NO_CACHE * FROM WorkingTable;
ALTER TABLE WorkingTable RENAME WorkingTableOld;
ALTER TABLE WorkingTableNew RENAME WorkingTable;
DROP TABLE WorkingTableOld;

Tapi tunggu !!! Bagaimana dengan data baru yang masuk saat memproses perintah ini ??? Mengganti nama tabel di awal operasi harus melakukan trik. Biarkan sedikit perubahan kode ini untuk mencegah memasukkan data baru dalam hal itu:

#
#  Script 3
#  Alter table structure of a single column of a large table
#  while preventing it from replicating to slaves
#  and preventing new data from entering into the old table
#
SET SQL_LOG_BIN = 0;
ALTER TABLE WorkingTable RENAME WorkingTableOld;
CREATE TABLE WorkingTableNew LIKE WorkingTableOld;
ALTER TABLE WorkingTableNew MODIFY BigColumn VARCHAR(50);
INSERT INTO WorkingTableNew SELECT SQL_NO_CACHE * FROM WorkingTableOld;
ALTER TABLE WorkingTableNew RENAME WorkingTable;
DROP TABLE WorkingTableOld;
  • Script 1 dapat dieksekusi pada slave apa pun yang tidak memiliki log biner diaktifkan
  • Script 2 dapat dieksekusi pada slave apa pun yang memiliki log biner diaktifkan
  • Script 3 dapat dieksekusi pada master atau di mana pun

Cobalah !!!

RolandoMySQLDBA
sumber
2
Satu masalah yang saya lihat adalah jika tabel berisi bidang 'auto_increment'. Saya melakukan tes dasar dan terkejut melihat bahwa CREATE TABLE .. LIKE tidak menyalin nilai auto_increment ke tabel baru
Derek Downey
1
@Dest: Tangkapan Bagus dan Komentar Hebat !!!. Saya yakin Anda dapat mengambil nilai auto_increment dari kolom information_schema.tables AUTO_INCREMENT. Jika tabel tidak memiliki bidang AUTO_INCREMENT, maka kolom AUTO_INCREMENT di information_schema.tables akan menjadi NULL. Jika tidak, ini akan berisi nilai AUTO_INCREMENT yang dibutuhkan. Saya kira itu dapat ditulis untuk mengekstrak nilai non-NULL dan melakukan ALTER TABLE WorkingSet AUTO_INCREMENT = <somenumber>; sesaat sebelum mengubah nama tabel temp kembali ke WorkingSet.
RolandoMySQLDBA
@RolandoMySQLDBA Hal lain yang dapat dilakukan adalah Anda membuat WorkingTableNew dengan menyalin pernyataan "show create table WorkingTable" dan ubah nilai bidang auto_increment dengan meneruskannya dengan angka yang aman untuk Anda, dan juga ubah kolom menjadi varchar (50 ), dan kemudian lakukan kedua ganti nama dalam pernyataan tunggal "rename table WorkingTable to WorkingTableOld, rename table WorkingTableNew to WorkingTable" Menjalankan kedua rename dalam satu perintah memastikan tidak ada insert yang gagal (diuji ini adalah tabel Prod yang mendapat 1000s insert / s) Kemudian Anda dapat melakukan perintah "masukkan ke ... dari"
Gautam Somani
4

Dugaan saya dari dokumentasi adalah bahwa hanya menambah batasan panjang pada a varchartidak akan menyebabkan masalah yang sama dengan menambahkan kolom:

Untuk beberapa operasi, ALTER TABLE di tempat dimungkinkan yang tidak memerlukan tabel sementara:

Tetapi hal itu sepertinya bertentangan dalam komentar di SO ini pertanyaan .

EDIT

Setidaknya pada 5.0, saya pikir saya dapat mengkonfirmasi bahwa menambah panjang memang membutuhkan tabel sementara (atau operasi lain yang sama-sama mahal):

testbed:

create table my_table (id int auto_increment primary key, varchar_val varchar(10));
insert into my_table (varchar_val)
select 'HELLO'
from (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s1,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s2,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s3,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s4,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s5,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s6;

hasil:

alter table my_table modify varchar_val varchar(20);
Query OK, 1000000 rows affected (2.91 sec)

alter table my_table add int_val int;
Query OK, 1000000 rows affected (2.86 sec)
Jack mengatakan coba topanswers.xyz
sumber
Mengubah ukuran bidang varchar melibatkan memeriksa bahwa Anda tidak melebihi ukuran baru. Untuk peningkatan ukuran cek ini DAPAT dioptimalkan pergi.
BillThor
3

Saya pikir, saya akan menyebutkan itu sejak ENGINE=INNODB

Jika Anda memiliki batasan kunci asing, Anda tidak dapat mengubah dan mengganti nama tanpa batasan yang menunjuk ke tabel lama (sekarang diganti nama). Anda harus mengubah sesudahnya atau menghapus batasan selama durasi.

randomx
sumber
Shabang !!! Itu sangat benar. Dalam hal ini, seseorang hanya bisa terjebak dengan melakukan tabel alter pada tabel asli. Paling tidak, Anda mungkin harus bermain game dengan kendala penonaktifan, sebelum ALTER TABLE berlangsung. +1 untuk tangkapan yang sangat bagus !!!
RolandoMySQLDBA