ALTER TABLE em uma tabela grande com uma coluna indexada


14

Eu tenho uma tabela grande com uma coluna VARCHAR (20) e preciso modificá-la para se tornar uma coluna VARCHAR (50). Normalmente, a execução de uma ALTER TABLE (adicionando um TINYINT) nessa tabela em particular leva cerca de 90 a 120 minutos para ser concluída. Por isso, só posso fazer isso no sábado ou domingo à noite para evitar afetar os usuários do banco de dados. Se possível, eu gostaria de fazer essa modificação antes de então.

A coluna também é indexada, o que eu presumo que tornará o ALTER TABLE mais lento, porque ele deve reconstruir o índice depois de modificar o comprimento da coluna.

O aplicativo da web é configurado em um ambiente de replicação do MySQL (26 escravos e um mestre). Lembro-me de ler uma vez em algum lugar que um método é primeiro executar a ALTER TABLE em cada escravo (minimizando o impacto sobre os usuários), depois fazer isso no mestre, mas isso não tentará replicar o comando ALTER TABLE para os escravos?

Portanto, minha pergunta é: qual é a melhor maneira de modificar essa tabela com o mínimo de interrupção para meus usuários?

Edit: a tabela é InnoDB.


adicionar essa coluna tinyint significava realmente adicionar uma coluna com um valor padrão? Porque fazer isso em uma enorme mesa poderia levar muito tempo ..
Marian

Respostas:


13

Se você é um pouco aventureiro, pode resolver o problema executando a ALTER TABLE nos estágios que pode ver. Suponha que a tabela que você deseja alterar seja chamada WorkingTable. Você pode executar as alterações em estágios como este:

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

Você pode fazer isso em todos os escravos. E o mestre ??? Como você evita que isso seja replicado para os escravos. Simples: não envie o SQL para os logs binários do mestre. Simplesmente desligue o log binário na sessão antes de executar o procedimento 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;

Mas espere !!! E quanto a novos dados que chegam ao processar esses comandos ??? Renomear a tabela no início da operação deve funcionar. Vamos alterar um pouco esse código para evitar a inserção de novos dados a esse respeito:

#
#  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;
  • O script 1 pode ser executado em qualquer escravo que não tenha logs binários ativados
  • O script 2 pode ser executado em qualquer escravo que possua logs binários habilitados
  • O script 3 pode ser executado em um mestre ou em qualquer outro lugar

De uma chance !!!


2
Um problema que vejo é se a tabela contém um campo 'auto_increment'. Eu fiz um teste de base e foi surpreendido ao ver que CREATE TABLE .. não gosta não copiar o valor auto_increment para a nova tabela
Derek Downey

1
@ Test: Boa captura e ótimo comentário !!!. Acredito que você pode recuperar o valor de incremento automático da coluna information_schema.tables AUTO_INCREMENT. Se a tabela não tiver um campo AUTO_INCREMENT, a coluna AUTO_INCREMENT em information_schema.tables será NULL. Caso contrário, ele conterá o valor AUTO_INCREMENT necessário. Eu acho que pode ser script para extrair esse valor não NULL e fazer ALTER TABLE WorkingSet AUTO_INCREMENT = <somenumber>; pouco antes de renomear a tabela temporária de volta para WorkingSet.
RolandoMySQLDBA

@RolandoMySQLDBA Outra coisa que pode ser feita é criar a WorkingTableNew copiando as instruções "show create table WorkingTable" e alterar o valor do campo auto_increment, promovendo-o por um número seguro para você e também alterar a coluna para varchar (50 ) e, em seguida, renomeie na instrução única "renomear tabela WorkingTable para WorkingTableOld, renomear tabela WorkingTableNew para WorkingTable" A execução de ambas as renomeações no comando único garante que nenhuma inserção falhe (testado, este é o Prod for table que recebe milhares de inserções / s). você pode fazer o "INSERT INTO ... a partir de" comando
Gautam Somani

4

Meu palpite na documentação seria que apenas aumentar a restrição de comprimento em um varcharnão causaria o mesmo problema que adicionar uma coluna:

Para algumas operações, é possível um ALTER TABLE local que não exija uma tabela temporária:

Mas isso parece ser contradito nos comentários sobre essa questão do SO .

EDITAR

Pelo menos no 5.0, acho que posso confirmar que o aumento do comprimento realmente exige uma tabela temporária (ou alguma outra operação igualmente cara):

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;

resultado:

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)

A modificação do tamanho do campo varchar envolve a verificação de que você não está excedendo o novo tamanho. Para aumentos de tamanho, essa verificação PODE ser otimizada.
BillThor

3

Eu pensei, eu mencionaria que desde o ENGINE=INNODB

Se você tiver restrições de chave estrangeira, não poderá alterar e renomear as restrições que apontam para a tabela antiga (agora renomeada). Você precisará alterar posteriormente ou eliminar as restrições durante o período.


Shabang !!! Isso é tão verdade. Nesse caso, só se pode ficar com a alteração da tabela na tabela original. No mínimo, talvez você precise jogar com restrições desabilitantes antes que o ALTER TABLE ocorra. +1 para essa captura muito boa !!!
RolandoMySQLDBA
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.