Como migrar banco MySQL entre servidores sem perda de dados
Migre um banco MySQL entre servidores Linux usando mysqldump, transferência segura e verificação de integridade — sem downtime descontrolado nem registros perdidos.
Migrar um banco MySQL entre servidores parece trivial até a primeira tentativa em produção. Um mysqldump rodado no horário errado pode capturar metade de uma transação, deixar dados inconsistentes ou travar o banco por minutos. Pior: na pressa, é comum esquecer triggers, stored procedures, eventos ou permissões — e descobrir só quando a aplicação quebra no servidor novo.
Este tutorial é pra sysadmins e desenvolvedores movendo um banco MySQL entre dois servidores Linux — seja por upgrade de hardware, mudança de provedor ou consolidação de infraestrutura. Cobrimos o caminho clássico (dump + transferência + restore) e quando vale a pena ir pra replicação assíncrona pra eliminar downtime quase completamente.
Tempo estimado de execução: 30-60 minutos pra bancos de até 10 GB usando dump tradicional. Acima disso, planeje uma janela maior ou considere replicação. Todo o procedimento é executado por SSH; assuma que você tem acesso root ou sudo nos dois servidores.
Pré-requisitos
Acesso SSH com sudo nos dois servidores (origem e destino), MySQL ou MariaDB instalado em ambos, espaço livre no destino equivalente a pelo menos 2× o tamanho atual do banco e firewall liberado entre os hosts (porta 3306 se for usar replicação, porta 22 pra transferência via SCP).
Confirme as versões antes de prosseguir. Dump de uma versão mais nova restaurado em versão mais antiga raramente funciona — sintaxe e tipos de dados divergem.
8.0.36 8.0.36 ou superior >= 2x tamanho do banco 22 Verifique a versão exata em cada lado com mysql --version antes de continuar. Para bancos com mais de 50 GB, leia primeiro a seção sobre replicação assíncrona no final — dump tradicional vira impraticável nesse volume.
Preparação no servidor de origem
Antes de tirar o dump, é crucial entender o que precisa ser migrado além das tabelas. Bancos reais têm usuários, permissões, eventos agendados, procedures e triggers que vivem fora dos arquivos .ibd da base alvo.
Liste os bancos e meça o tamanho real:
mysql -uroot -p -e "SELECT table_schema AS db, ROUND(SUM(data_length + index_length)/1024/1024, 2) AS size_mb FROM information_schema.tables GROUP BY table_schema;"Anote o tamanho do banco que você vai migrar — você precisa disso pra dimensionar o espaço no destino e estimar o tempo de dump (~1-2 minutos por GB em disco rápido).
Verifique quais engines estão em uso:
mysql -uroot -p -e "SELECT table_schema, engine, COUNT(*) FROM information_schema.tables WHERE table_schema = 'meubanco' GROUP BY table_schema, engine;"Se aparecer MyISAM no resultado, considere converter pra InnoDB antes de migrar. MyISAM não suporta --single-transaction, forçando o dump a usar lock de tabela que bloqueia escritas.
Exporte os usuários e permissões em arquivo separado:
mysql -uroot -p -e "SELECT CONCAT('SHOW GRANTS FOR ''', user, '''@''', host, ''';') FROM mysql.user WHERE user NOT IN ('mysql.sys','mysql.session','mysql.infoschema','root');" -s --skip-column-names | mysql -uroot -p -s --skip-column-names | sed 's/$/;/' > grants.sqlO dump padrão do mysqldump não traz os usuários — eles vivem em mysql.user, fora do banco da aplicação. Sem esse arquivo, você restaura os dados mas as conexões da aplicação falham com “Access denied”.
Gerando o dump consistente
Esta é a etapa mais sensível. Um dump mal-feito captura dados parcialmente atualizados e gera inconsistências invisíveis até a aplicação quebrar com chaves estrangeiras inválidas.
Gere o dump com flags de consistência:
mysqldump -uroot -p \
--single-transaction \
--routines \
--triggers \
--events \
--master-data=2 \
--hex-blob \
--default-character-set=utf8mb4 \
meubanco > meubanco_$(date +%Y%m%d_%H%M%S).sqlCada flag cobre um caso específico: --single-transaction garante snapshot consistente sem lock; --routines --triggers --events incluem procedures, triggers e eventos agendados; --master-data=2 grava a posição do binlog como comentário (útil pra replicação posterior); --hex-blob evita corrupção de campos BLOB durante a transferência.
Compacte o dump pra acelerar a transferência:
gzip -9 meubanco_*.sqlCompressão gzip reduz dumps SQL em 70-85% tipicamente. Em bancos grandes, isso transforma uma transferência de 40 minutos em 8 minutos — vale o tempo de CPU.
Sem essa flag, o dump captura cada tabela num momento diferente. Se a aplicação está escrevendo, você acaba com pedido na tabela orders que referencia produto inexistente em products. O restore aceita, mas a aplicação quebra ao tentar carregar o pedido.
Transferindo o dump pro servidor destino
Pra dumps até 10 GB, SCP é simples e seguro. Acima disso, considere rsync com retomada ou transferência em chunks paralelos.
Transfira via SCP do servidor de origem:
scp meubanco_*.sql.gz [email protected]:/tmp/Se a conexão é instável ou o arquivo passa de 5 GB, prefira rsync --partial --progress — ele retoma de onde parou se cair no meio.
No servidor de destino, valide o checksum:
# No origem:
sha256sum meubanco_*.sql.gz
# No destino:
sha256sum /tmp/meubanco_*.sql.gzCompare os hashes. Diferença significa corrupção na transferência — repita o SCP/rsync antes de restaurar. Restaurar dump corrompido gera erros aleatórios meio do caminho e exige refazer tudo.
Restore no servidor de destino
Antes de despejar o SQL, prepare o ambiente: confirme charset, ajuste buffers e desabilite checks que atrasam o restore.
Crie o banco vazio com charset correto:
mysql -uroot -p -e "CREATE DATABASE meubanco CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"Charset errado é fonte clássica de ”?????” no lugar de acentos. Se o origem usa utf8mb4, o destino tem que usar utf8mb4 — não utf8 (que é 3 bytes e quebra emoji).
Restaure o dump com flags de performance:
gunzip < /tmp/meubanco_*.sql.gz | mysql -uroot -p \
--max_allowed_packet=512M \
--init-command="SET autocommit=0; SET unique_checks=0; SET foreign_key_checks=0;" \
meubancoDesabilitar unique_checks e foreign_key_checks durante o restore acelera em 3-5×. O dump é internamente consistente, então não há risco. Os checks voltam ao default na próxima conexão.
Restaure usuários e permissões:
mysql -uroot -p < grants.sql
mysql -uroot -p -e "FLUSH PRIVILEGES;"FLUSH PRIVILEGES força o MySQL a reler a tabela de permissões. Sem isso, alguns clientes continuam recebendo “Access denied” mesmo com os grants já inseridos.
Verificação de integridade
Restaurou sem erro não significa que está completo. Sempre valide contagens e estrutura antes de apontar a aplicação.
Compare contagem de linhas tabela por tabela:
# No origem:
mysql -uroot -p -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='meubanco' ORDER BY table_name;" > origem_counts.txt
# No destino:
mysql -uroot -p -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='meubanco' ORDER BY table_name;" > destino_counts.txt
diff origem_counts.txt destino_counts.txttable_rows em information_schema é estimativa pra InnoDB — pode divergir levemente. Pra contagem exata em tabelas críticas, rode SELECT COUNT(*) FROM tabela em ambos os lados.
Verifique objetos auxiliares:
mysql -uroot -p meubanco -e "SHOW TRIGGERS\G SHOW PROCEDURE STATUS WHERE Db='meubanco'\G SHOW EVENTS\G"Triggers, procedures e eventos só vêm no dump se você usou as flags --triggers --routines --events. Se algum estiver faltando, recoloque manualmente via dump específico (mysqldump --no-data --no-create-info --triggers ...).
Pra bancos acima de 50 GB ou aplicações que não toleram janela de downtime, configure o destino como réplica do origem via CHANGE MASTER TO ... MASTER_LOG_FILE=... MASTER_LOG_POS=... usando a posição gravada pelo --master-data=2. Quando Seconds_Behind_Master chega a zero, você redireciona a aplicação e promove a réplica. Downtime real fica em segundos.
Resolução de problemas
Erro “MySQL server has gone away” durante o restore
O dump tem statements maiores que o max_allowed_packet configurado no destino. Aumente temporariamente pra 512M ou 1G via --max_allowed_packet no comando ou em /etc/mysql/my.cnf antes de tentar de novo.
Charset volta como “latin1” mesmo com banco em utf8mb4
O cliente MySQL no destino está conectando com charset diferente do banco. Force com --default-character-set=utf8mb4 no comando de restore. Confirme depois com SHOW VARIABLES LIKE 'character_set%'.
Foreign keys quebradas após o restore
Aconteceu se você não usou --single-transaction no dump ou se misturou tabelas InnoDB e MyISAM. Verifique via SHOW ENGINE INNODB STATUS e considere refazer o dump com a flag correta — corrigir manualmente é demorado e propenso a erro.
Próximos passos
Com a migração concluída, considere automatizar backups recorrentes no novo servidor pra evitar ter que improvisar quando precisar de outra migração ou restore de emergência. Documente a configuração de innodb_buffer_pool_size e ajuste pra refletir a RAM disponível no servidor novo — manter o default de 128 MB em servidor com 16 GB de RAM desperdiça performance.
Se você está consolidando bancos em produção, uma VPS Hostini com SSD NVMe oferece IOPS suficientes pra MySQL sob carga sem precisar de tuning agressivo de cache. A latência consistente também ajuda em cenários de replicação assíncrona entre regiões.
Próximos tópicos pra aprofundar: configuração de replicação MySQL master-replica, tuning de innodb_buffer_pool_size baseado em workload real, backups incrementais com Percona XtraBackup e monitoramento de queries lentas via slow log.
Perguntas frequentes
Posso migrar MySQL entre versões diferentes (ex: 5.7 para 8.0)?
Sim, mas só upgrade (5.7 → 8.0), nunca downgrade. Use mysqldump no servidor origem e restore no destino — o formato SQL é compatível. Após o restore, rode mysql_upgrade no destino 5.7→8.0 pra atualizar tabelas do sistema. Teste autenticação: MySQL 8 usa caching_sha2_password por default, o que pode quebrar drivers antigos.
Qual a diferença entre mysqldump e mysqlpump?
mysqldump é single-threaded e estável desde versões antigas. mysqlpump é multi-threaded e mais rápido em bases grandes, mas tem limitações com triggers e routines em algumas versões. Pra migrações críticas com até 50 GB, mysqldump com --single-transaction é mais previsível. Acima disso, considere Percona XtraBackup pra evitar lock de leitura.
Como migrar sem qualquer downtime?
Configure o servidor destino como réplica do origem usando replicação assíncrona. Depois que o destino alcança o origem (Seconds_Behind_Master = 0), você faz o switchover redirecionando a aplicação pro novo servidor e promove a réplica a primária. A janela real de downtime fica em segundos — apenas o tempo de trocar o endpoint.
mysqldump trava o banco durante o dump?
Com --single-transaction em tabelas InnoDB, não trava — usa MVCC pra ler um snapshot consistente sem bloquear escritas. Pra MyISAM, o dump usa LOCK TABLES e bloqueia escritas durante a leitura. Misturar InnoDB e MyISAM no mesmo dump compromete a consistência: migre o esquema pra InnoDB antes.
Por que o tamanho do .sql é diferente do tamanho real do banco?
O dump é texto SQL puro, sem índices materializados nem páginas binárias. Geralmente fica 30-60% do tamanho dos arquivos .ibd. Após o restore, o tamanho em disco volta ao normal porque o MySQL reconstrói índices e estatísticas. Não confunda tamanho do dump com tamanho real do banco.
Como verificar que nenhuma linha foi perdida na migração?
Conte linhas tabela por tabela em ambos os servidores com SELECT COUNT(*) e compare. Pra integridade mais profunda, gere checksums com pt-table-checksum (do Percona Toolkit) ou compare CHECKSUM TABLE pra cada tabela. Se as contagens batem mas há suspeita de dados truncados, compare também SUM() de colunas numéricas críticas.