Manutenção Por Equipe DevBras

Capa do artigo: Backup MySQL: por que copiar a pasta data falha

Backup MySQL: por que copiar a pasta data falha

Backup MySQL copiando a pasta data com o banco ligado, testado no 8.4.13: cópia que não sobe, cópia que sobe com saldo errado e o jeito certo de copiar.

Copiar /var/lib/mysql com o servidor no ar dá uma pasta inteira, sem erro, do tamanho certo, e por isso parece backup. Para ver o que essa cópia vale, rodamos o teste num MySQL 8.4.13 em container, com uma carga parecida com a de uma loja: transferências entre 1.000 contas (o total dos saldos é sempre 1.000.000) e pedidos com dois itens cada. Enquanto a carga rodava, copiamos a pasta de dados arquivo por arquivo, como faz um rsync agendado ou o botão "Fazer backup" de um sistema desktop. Fizemos isso três vezes.

  • Na primeira e na terceira cópias, o MySQL não subiu. O log disse Found existing redo log files, but at least one is missing. It is unknown if recovery could reach physically consistent state e terminou em Data Dictionary initialization failed.
  • A segunda subiu. O CHECK TABLE deu OK nas três tabelas, e a soma dos saldos deu 999.993. Sumiram 7 reais: um débito entrou na cópia sem o crédito da mesma transação.

A segunda é a pior das três. Um backup que não sobe pelo menos avisa no dia do restore. O que sobe com dado errado passa em qualquer verificação superficial e só aparece quando alguém fecha o caixa. A mesma cópia lenta, arquivo por arquivo, feita com o servidor parado, subiu com a soma exata e com o número de itens igual ao dobro do de pedidos.

Por que a cópia com o banco ligado se desmonta

O InnoDB não grava cada mudança direto no arquivo da tabela. A alteração vai para as páginas em memória e para o redo log, e as páginas vão para o disco depois, aos poucos. No 8.4.13 do teste, o redo fica na pasta #innodb_redo, com 32 arquivos que o servidor cria, enche e recicla (os de nome _tmp são os de reserva), dentro de uma capacidade padrão de 100 MB.

Uma cópia leva segundos ou minutos. Cada arquivo sai num momento diferente, e um arquivo de redo pode ser renomeado ou reciclado entre o começo e o fim da cópia. O resultado é uma colagem de instantes que nunca existiu. Às vezes faltam arquivos de redo e o servidor se recusa a subir, como nas cópias 1 e 3. Às vezes a recuperação consegue montar algo que parece coerente, mas com páginas de momentos diferentes, como na cópia 2. Nada disso muda se você copiar com cp, rsync, tar, docker cp ou pelo explorador de arquivos do Windows.

Com o servidor desligado de forma limpa, o InnoDB grava tudo antes de sair, e a pasta fica parada. Por isso a cópia a frio funcionou mesmo sendo lenta.

As formas de cópia física que funcionam

Se a ideia era copiar arquivos (porque restaurar arquivos é mais rápido que reexecutar SQL), há três caminhos seguros. O dump lógico é o quarto, e ele tem um guia próprio, backup de MySQL que você consegue restaurar. No teste, um mysqldump --single-transaction gerado com a mesma carga rodando voltou com a soma exata e com dois itens por pedido.

Cópia a frio, com parada curta

Funciona para bases que aguentam alguns minutos fora do ar, como um sistema interno fora do expediente ou um servidor pequeno antes de uma atualização:

sudo systemctl stop mysql        # mysqld no RHEL e derivados
sudo rsync -a /var/lib/mysql/ /backup/mysql-frio-$(date +%F)/
sudo systemctl start mysql

Em Docker, a regra é a mesma. Pare o container antes de copiar o volume, porque docker cp ou um tar do volume com o banco no ar dão o mesmo resultado das cópias 1 a 3:

docker stop -t 60 loja-mysql
docker run --rm -v loja-mysql-dados:/src:ro -v /backup:/dst alpine:3.22 \
  tar czf /dst/mysql-frio-$(date +%F).tgz -C /src .
docker start loja-mysql

O -t 60 dá ao MySQL até 60 segundos para terminar de gravar antes de o Docker forçar a parada. O padrão é 10 segundos, pouco para um buffer pool grande. Num painel como o Dockyard, parar a stack não apaga os volumes, então o roteiro é parar, copiar o volume e subir de novo. A cópia física só volta numa instalação com a mesma versão do MySQL.

Snapshot do volume inteiro, no mesmo instante

Um snapshot de LVM, ZFS ou do disco do provedor de nuvem congela todos os arquivos no mesmo instante. Para o InnoDB, isso equivale a uma queda de energia, e é dessa situação que a recuperação dele foi feita para dar conta. O cuidado é que o snapshot tem de cobrir dados, redo e undo juntos. Se a pasta #innodb_redo ou os logs estão em outro disco, dois snapshots separados voltam ao problema da colagem de instantes. Mesmo com um volume só, teste o restore, porque um snapshot que nunca foi aberto ainda não prova nada.

Cópia a quente com XtraBackup

Para bases grandes que não podem parar, o Percona XtraBackup copia os arquivos com o servidor no ar e registra o redo gerado durante a cópia. A documentação do preparo descreve o mesmo problema do nosso teste: os arquivos foram copiados em momentos diferentes, e o InnoDB detecta corrupção se você tentar subir com eles. A etapa --prepare aplica o redo e deixa tudo consistente num único instante:

xtrabackup --defaults-extra-file=/etc/loja/backup.cnf --backup --target-dir=/backup/xb/$(date +%F)
xtrabackup --prepare --target-dir=/backup/xb/$(date +%F)

A versão importa. Segundo a tabela de compatibilidade da Percona, o XtraBackup 8.4 faz backup de servidores 8.4.x e para com erro se a versão do servidor não bater. Isso conta na hora de escolher a imagem do banco: no Docker Hub, em 31/10/2026, a tag mysql:8.4 apontava para a 8.4.13, enquanto mysql:lts já apontava para a 9.7.4 e mysql:latest para a 26.10.0 (série innovation). Um mysql:latest no Compose pode deixar o XtraBackup para trás numa atualização qualquer. O post sobre a tag latest no Docker mostra o porquê.

Se o backup sai de um botão no sistema

Em sistemas desktop é comum o próprio programa ter o botão de backup, e é aí que a cópia da pasta costuma morar. A troca é chamar um script que gera o dump e falha de forma visível. Esta versão foi testada no container do MySQL 8.4.13 com bash 5:

#!/bin/bash
# uso: backup-loja.sh /etc/loja/backup.cnf /var/backups/mysql
set -euo pipefail
CNF=$1; DESTINO=$2
ARQ="$DESTINO/loja-$(date +%F-%H%M).sql.gz"
if ! mysqldump --defaults-extra-file="$CNF" --single-transaction --routines --triggers --events loja | gzip > "$ARQ.parcial"; then
  rm -f "$ARQ.parcial"; echo "FALHOU: dump não gerado" >&2; exit 1
fi
gzip -t "$ARQ.parcial"
zcat "$ARQ.parcial" | tail -1 | grep -q '^-- Dump completed' || { echo "FALHOU: dump sem a linha final" >&2; exit 1; }
mv "$ARQ.parcial" "$ARQ"
echo "ok: $ARQ ($(du -h "$ARQ" | cut -f1))"

O detalhe que mais passa batido é o pipefail. Sem ele, o código de saída de mysqldump | gzip é o do gzip. No teste com a senha errada, a linha sem pipefail devolveu 0 e deixou um arquivo .sql.gz de 20 bytes, vazio por dentro, com cara de backup. Com o script acima, a mesma senha errada deu FALHOU: dump não gerado, código 1, e nenhum arquivo. O .parcial evita que um dump interrompido fique com o nome de um bom, e a última linha (-- Dump completed) confirma que o mysqldump chegou ao fim. A senha fica no arquivo do --defaults-extra-file, com permissão 600, e não na linha de comando. Essa opção precisa vir antes das outras.

O programa só precisa chamar o script e mostrar a mensagem de erro quando o código de saída não for 0. E o arquivo tem de ir para fora da máquina do banco, ou ele se perde junto com o disco.

Como saber se a cópia vale

A cópia 2 mostra que "o MySQL subiu" e "CHECK TABLE OK" não bastam. O teste de restore precisa conferir alguma regra do negócio que você sabe ser verdadeira. No nosso caso, a soma dos saldos é constante e todo pedido tem itens. Numa loja real, pode ser o total do dia no financeiro ou o último número de nota fiscal emitida. Restaure num MySQL vazio da mesma versão, rode essas consultas e anote o tempo que levou. Esse tempo é o mínimo que a operação vai ficar parada no dia ruim.

Fazer isso uma vez já ensina muito. Para que vire rotina sem depender de ninguém lembrar, veja como montar o ensaio de restore automático. Depois, deixe os comandos escritos no runbook de queda, para que quem estiver de plantão às 3h não precise improvisar.

Avalie este artigo

Referência: http://www.portugal-a-programar.pt/forums/topic/45409-backup-e-restore-bd-em-mysql-em-vbnet/