MySQL - o caminho mais rápido para ALTER TABLE for InnoDB

12

Eu tenho uma tabela do InnoDB que desejo alterar. A tabela possui ~ 80 milhões de linhas e encerra alguns índices.

Quero alterar o nome de uma das colunas e adicionar mais alguns índices.

  • Qual é a maneira mais rápida de fazer isso (supondo que eu possa sofrer até um tempo de inatividade - o servidor é um escravo não utilizado)?
  • É uma alter tablesolução simples , a mais rápida?

Neste momento, tudo o que me interessa é a velocidade :)

Correu
fonte
Por favor SHOW CREATE TABLE tblname\G, mostre a coluna que precisa ser alterada, o tipo de dados da coluna e o novo nome para a coluna.
RolandoMySQLDBA
aqui está: pastie.org/3078349 a coluna que precisa ser renomeada é sent_ate para adicioná-la mais alguns índices # : 277
Ran
sent_at precisa ser renomeado para quê?
RolandoMySQLDBA
Vamos dizer: new_sent_at
Ran

Respostas:

14

Uma maneira segura de acelerar um ALTER TABLE é remover índices desnecessários

Aqui estão as etapas iniciais para carregar uma nova versão da tabela

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
;

Por favor observe o seguinte:

  • Larguei source_persona_index porque é a primeira coluna em outros 4 índices

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Larguei target_persona_index porque é a primeira coluna em outros 2 índices

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Larguei target_persona_relation_type_index porque as 2 primeiras colunas também estão em target_persona_relation_type_message_id_index

OK Isso cuida de índices desnecessários. Existem índices com baixa cardinalidade? Aqui está o caminho para determinar isso:

Execute as seguintes consultas:

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;

De acordo com sua pergunta, existem cerca de 80.000.000 de linhas. Como regra geral, o MySQL Query Optimizer não usará um índice se a cardinalidade das colunas selecionadas for maior que 5% da contagem de linhas da tabela. Nesse caso, isso seria 4.000.000.

  • Se COUNT(DISTINCT sent_at)> 4.000.000
    • então ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Se COUNT(DISTINCT message_id)> 4.000.000
    • então ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Se COUNT(DISTINCT target_object_id)> 4.000.000
    • então ALTER TABLE s_relations_new DROP INDEX target_object_index;

Depois de determinar a utilidade ou inutilidade desses índices, você pode recarregar os dados

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

É isso aí, né? NÃO !!!

Se o seu site estiver ativo o tempo todo, pode haver INSERTs sendo executados contra s_relations durante o carregamento de s_relations_new. Como você pode recuperar essas linhas ausentes?

Vá encontrar o ID máximo em s_relations_new e anexe tudo depois desse ID em s_relations. Para garantir que a tabela esteja congelada e usada apenas para esta atualização, você deve ter um pouco de tempo de inatividade para obter as últimas linhas que foram inseridas em s_relation_new. Aqui está o que você faz:

No sistema operacional, reinicie o mysql para que ninguém mais possa fazer login, exceto root @ localhost (desativa o TCP / IP):

$ service mysql restart --skip-networking

Em seguida, efetue login no mysql e carregue as últimas linhas:

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;

Então, reinicie o mysql normalmente

$ service mysql restart

Agora, se você não pode derrubar o mysql, precisará fazer uma isca e alternar em s_relations. Apenas entre no mysql e faça o seguinte:

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;

De uma chance !!!

CAVEAT: Quando estiver satisfeito com esta operação, você poderá soltar a tabela antiga assim que possível:

mysql> DROP TABLE s_relations_old;
RolandoMySQLDBA
fonte
12

A resposta correta depende da versão do mecanismo MySQL que você está usando.

Se você estiver usando o 5.6+, renomear e adicionar / remover índices são executados online , ou seja, sem copiar todos os dados da tabela.

Apenas use ALTER TABLE como de costume, será quase instantâneo para renomear e eliminar índices e razoavelmente rápido para adicionar índices (tão rápido quanto ler uma vez toda a tabela).

Se você estiver usando 5.1+ e o plug-in InnoDB estiver ativado, a adição / remoção de índices também estará online. Não tenho certeza sobre renomear.

Se estiver usando a versão mais antiga, ALTER TABLEainda será o mais rápido - mas provavelmente será terrivelmente lento porque todos os seus dados serão reinseridos em uma tabela temporária sob o capô.

Finalmente, é hora de desmistificar mitos. Infelizmente, não tenho carma suficiente aqui para comentar as respostas, mas acho importante corrigir a resposta mais votada. Isto está errado :

Como regra geral, o MySQL Query Optimizer não usará um índice se a cardinalidade das colunas selecionadas for maior que 5% da contagem de linhas da tabela

Na verdade, é o contrário .

Os índices são úteis para selecionar poucas linhas, por isso é importante que eles tenham alta cardinalidade, o que significa muitos valores distintos e estatisticamente poucas linhas com o mesmo valor.

mezis
fonte
Link para a documentação do plugin InnoDB (não foi possível colar devido aos limites de representantes).
Me # 613
2
No MySQL 5.5, achei RENAME TABLEinstantâneo (conforme o esperado), mas CHANGE COLUMNa renomear a chave primária fez uma cópia completa ... 7 horas! Possivelmente apenas porque era a chave primária? Não é bom.
KCD
2

Eu tive o mesmo problema com o Maria DB 10.1.12. Depois de ler a documentação, descobri que há uma opção para executar a operação "no local", que elimina a cópia da tabela. Com esta opção, a tabela de alteração é muito rápida. No meu caso, foi:

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

isso é muito rápido. Sem a opção de algoritmo, nunca terminaria.

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

Saule
fonte
0

Para a coluna renomear,

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

deve estar bem e não deve ter nenhum tempo de inatividade.

Para os índices, a instrução CREATE INDEX bloqueará a tabela. Se é um escravo não utilizado, como você mencionou, isso não é problema.

Uma outra opção seria criar uma tabela totalmente nova com nomes e índices de coluna adequados. Em seguida, você pode copiar todos os dados para ele e executar uma série de

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

Isso minimizaria o tempo de inatividade com o custo de usar temporariamente o dobro do espaço.

TetonSig
fonte
1
DDL no MySQL não é transacional. Cada instrução DDL aciona um COMMIT. Eu escrevi sobre isso: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA
0

Eu também tenho esse problema e usei este 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;   

Espero que ajude alguém

Saudações,

Vai

William Rossier
fonte