O mysqldump terminou sem reclamar de uma linha sequer, e por um segundo bate aquela paz de quem acha que o trabalho acabou. Aí o arquivo é apontado pro RDS novinho, o enter é apertado com confiança, e o import morre logo na primeira function com um erro sobre um privilégio SUPER que ninguém tem e um root@localhost que no RDS simplesmente não existe. O dump foi honesto. O destino é que joga por outras regras. Este post percorre a migração de um banco MySQL 5 para o MySQL 8 rodando no RDS, e as três ou quatro pegadinhas que transformam um “é só copiar os dados” numa tarde inteira de investigação.
TL;DR: Exportar um banco MySQL 5 com
mysqldumpe importar limpo no MySQL 8 do Amazon RDS, contornando GTID, DEFINER, criação de function e a confusão de nomes do charset utf8.
Stack: MySQL 5.x, MySQL 8 no Amazon RDS, mysqldump, InnoDB, bash, sed
Nível: Intermediário
Tempo de leitura: ~6 min
Vale entender o que está de fato acontecendo, porque um dump-and-restore (gerar o arquivo na origem e recarregá-lo no destino) entre MySQL 5 e 8 no RDS cruza três fronteiras de uma vez, e cada uma morde de um jeito. Antes de tudo, uma sigla que vai aparecer bastante: RDS é o Relational Database Service, o serviço de banco relacional gerenciado da AWS, ou seja, um MySQL que a Amazon administra pra você e, em troca, tranca algumas portas por baixo. A primeira fronteira é a de replicação: um dump moderno carrega o GTID (Global Transaction Identifier, o identificador global de transação que o MySQL usa pra rastrear cada transação na replicação), e uma instância RDS recém-criada não tem nada que ver com essa herança. A segunda é a de propriedade: toda procedure, function e view vem estampada com um DEFINER (o “dono” registrado daquele objeto), e o RDS não te entrega a conta root@localhost pra onde esse carimbo normalmente aponta. A terceira é a de codificação: entre o MySQL 5 e o 8, o nome utf8 mudou de significado sem avisar ninguém. Nenhuma dessas é um bug. São as costuras entre duas versões do banco e uma plataforma gerenciada que de propósito não te dá o privilégio SUPER (a permissão administrativa mais alta do MySQL), e quem não sabe que as costuras existem vê o import falhar em lugares que parecem não ter nada a ver com a causa real.

Já fiz essa migração mais de uma vez, e da primeira eu fui no escuro. O dump estava limpo, a rede estava aberta, e o restore mesmo assim caiu de cara numa function que a aplicação quase nem usava. Depois de vinte minutos xingando um erro incompreensível, a solução era um único parâmetro do RDS e uma linha de sed. A ordem abaixo é justamente a que evita esses vinte minutos.
Gerar o dump sem travar a produção
Tudo começa na origem, e num banco vivo a forma como os dados são lidos importa mais do que o que é lido. Use --single-transaction, que faz o dump ler um retrato consistente dentro de uma única transação do InnoDB (o storage engine, ou motor de armazenamento, padrão do MySQL, e o que dá a ele transações de verdade) em vez de travar tabela por tabela enquanto trabalha. É exatamente por isso que a coisa é segura num servidor movimentado: o InnoDB te entrega uma foto congelada dos dados num instante único, e quem estiver gravando continua gravando nas tabelas vivas o tempo todo.
mysqldump --single-transaction \
--set-gtid-purged=OFF \
--routines \
--default-character-set=utf8mb4 \
bd_prod > bd_prod.sql
Cada flag está ganhando o próprio lugar. O --set-gtid-purged=OFF remove o bloco de GTID pra que o RDS não tente adotar a identidade de replicação da origem. O --routines puxa junto suas procedures e functions, que um dump comum deixa pra trás. E o --default-character-set=utf8mb4 controla só a conexão usada pra ler os dados, então ler pelo charset mais largo garante que nada se corrompa na saída.
Tirar o DEFINER antes do RDS ver
Essa é a que pega a maioria desprevenida. Como o dump saiu com --routines, cada procedure e function vem marcada com DEFINER=root@localhost. Essa conta não existe no RDS, então na hora de criar o objeto ele recusa e trava tudo. A saída mais limpa é neutralizar o carimbo no arquivo antes de carregar:
sed -i 's/DEFINER=[^*]*\*/\*/g' bd_prod.sql
Isso remove a marca de propriedade sem encostar no corpo das suas rotinas. Os objetos passam a ser criados como propriedade de quem roda o import, que no RDS é exatamente o que você quer.
Deixar o RDS criar as suas functions
Mesmo com o DEFINER fora do caminho, as stored functions batem numa segunda parede. Com o binary logging ligado (o binlog é o log binário que registra toda alteração de dados, e no RDS ele vive sempre ligado), o MySQL se recusa a criar uma function a menos que confie que ela é determinística, e sinaliza isso com mais uma reclamação de privilégio SUPER. Como no RDS não dá pra se conceder SUPER, a alavanca é um parâmetro. No parameter group (o grupo de parâmetros de configuração ligado à sua instância) você ajusta:
log_bin_trust_function_creators = 1
Aplique, deixe a instância pegar o valor, e a criação de functions para de brigar com você. É um ajuste único na instância, não algo que se mexe no dump.
Abrir a porta
Nada do que veio acima adianta se você não consegue chegar no banco. Por padrão, uma instância RDS mora dentro de uma VPC (Virtual Private Cloud, a rede privada e isolada que a AWS te dá dentro da nuvem dela) e o security group dela (o grupo de segurança, que funciona como um firewall na frente da instância) não confia em ninguém. Pra importar da sua própria máquina, o security group precisa liberar tráfego de entrada na porta 3306 (a porta padrão do MySQL) vindo do seu IP, e a instância precisa de fato estar alcançável de onde você está. Se ela estiver trancada numa subnet privada, não insista: rode o import de dentro de uma EC2 (Elastic Compute Cloud, a máquina virtual da AWS) na mesma VPC, onde o caminho de rede já existe.
Cuidar da renomeação do charset
Antes de carregar, resolva a codificação, porque é aqui que MySQL 5 e 8 discordam no vocabulário. Se o banco de origem reporta utf8mb3 / utf8mb3_general_ci, espelhe isso no destino pra que o padrão do banco combine com o que os seus CREATE TABLE já carregam. Vale saber de onde vem essa bagunça: o antigo utf8 do MySQL nunca foi UTF-8 de verdade, ele parava em três bytes por caractere, e é por isso que emoji e alguns símbolos sumiam calados havia anos, até o MySQL 8 finalmente rebatizar essa meia-bomba como utf8mb3, marcá-la como obsoleta e apontar todo mundo pro genuíno utf8mb4, de quatro bytes. Então, se um servidor mais antigo recusar utf8mb3_general_ci, saiba que utf8_general_ci é o mesmo collation com o nome de antes. Espelhe a origem agora, modernize pro utf8mb4 outro dia.
CREATE DATABASE bd_prod CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci;
Carregar
Crie o banco vazio primeiro, já que um dump de banco único costuma omitir a linha CREATE DATABASE, e então despeje o arquivo dentro dele:
mysql -h sua-instancia.xxxx.rds.amazonaws.com \
-u admin -p bd_prod < bd_prod.sql
Com o DEFINER removido, o parâmetro de function ligado e o charset combinado, isso roda até o fim em vez de emperrar na primeira rotina. Quando terminar, confira: compare a contagem de SHOW TABLES; entre origem e destino, verifique se as rotinas chegaram com SHOW PROCEDURE STATUS WHERE Db = 'bd_prod';, e cheque por amostragem a contagem de linhas de uma ou duas tabelas grandes. O dump não se verifica sozinho, então a conferida fica com você.
O que você acabou de fazer
Você tirou uma foto consistente de um banco MySQL 5 vivo, via InnoDB, sem travar quem estava gravando, apagou os carimbos de DEFINER que fariam o RDS engasgar, virou o único parâmetro que deixa o RDS criar suas functions, abriu um caminho de rede pelo security group e casou o charset pra que o MySQL 8 e suas tabelas antigas falem a mesma codificação. Os dados que moravam no servidor de outra pessoa agora rodam numa instância gerenciada que é sua, e as rotinas vieram junto na viagem em vez de ficarem esquecidas na porta.
Próximos passos
- Modernizar a codificação: Com a migração estável, planeje a troca de
utf8mb3parautf8mb4, pra que o banco consiga guardar de fato tudo que promete, emoji incluído. - Resolver o plugin de autenticação cedo: O MySQL 8 usa por padrão o
caching_sha2_password(o novo esquema de autenticação de senha baseado em SHA-256), e clientes ou drivers mais antigos podem precisar domysql_native_passwordou de um conector atualizado antes de conseguir logar. - Automatizar o dump: Junte a exportação, a passada de
sede o restore num único script, pra que a próxima migração seja um comando em vez de um checklist que você lembra pela metade. - Trancar a porta de novo: Se você abriu o security group pro seu IP durante o import, feche depois. Banco exposto pra internet é uma história que nunca termina bem.
Dúvidas ou sugestões? Me acha no LinkedIn ou no GitHub.