Backup no MariaDB sem parar o banco: mariadb-backup, incremental e restauração até o minuto exato

O dump funciona muito bem até o dia em que você precisa restaurar 300 GB com a diretoria olhando por cima do seu ombro.
Resumo para quem tem pressa: o MariaDB traz de graça uma ferramenta de backup físico a quente, o mariadb-backup. Ela copia os arquivos do banco com ele rodando, sem desligar o servidor, mas com bloqueios em determinadas etapas, e restaura muito mais rápido do que um dump SQL. Combinando um backup completo semanal, incrementais diários e o binlog, você consegue voltar o banco para qualquer minuto, por exemplo um segundo antes daquele DELETE sem WHERE. Neste artigo eu mostro os comandos, um script pronto para agendar e, o mais importante, como testar se o restore funciona.
Dump lógico x backup físico: qual é a diferença
Muita gente faz backup assim e dorme tranquila:
mariadb-dump --single-transaction --all-databases > backup.sql
Para bancos pequenos, é perfeito. O problema aparece com o crescimento. O dump gera comandos SQL, e restaurar significa reexecutar cada INSERT e reconstruir cada índice. Em bases de centenas de GB, isso leva horas, às vezes um dia inteiro.
O mariadb-backup faz outra coisa: copia os arquivos de dados do InnoDB enquanto o banco está no ar e registra tudo o que mudou durante a cópia, para deixar o resultado consistente. Restaurar é, basicamente, devolver os arquivos para o lugar.
mariadb-dump (lógico) |
mariadb-backup (físico) |
|
|---|---|---|
| Banco precisa parar? | Não; consistência com --single-transaction exige tabelas transacionais e cuidado com DDL |
Não |
| Velocidade do backup | Lenta em bases grandes | Rápida, limitada pelo disco |
| Velocidade do restore | Muito lenta (reexecuta SQL e recria índices) | Rápida (copia arquivos) |
| Backup incremental | Não | Sim |
| Restaurar só uma tabela | Fácil | Possível, porém mais trabalhoso |
| Portável para outra versão ou banco | Sim | Não é portável como SQL: exige compatibilidade de versão e formato físico |
A regra prática que uso nos clientes: dump para portabilidade e cópias pontuais, mariadb-backup para a recuperação de desastre. Os dois convivem bem.
Antes de tudo: um usuário só para backup
Não use root em script agendado. Crie um usuário com o mínimo necessário:
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'troque-esta-senha';
-- Privilégios mínimos para o mariadb-backup (10.5 em diante)
GRANT RELOAD, PROCESS, LOCK TABLES, BINLOG MONITOR
ON *.* TO 'backup'@'localhost';
-- Só se for fazer backup a partir de uma réplica (--slave-info)
GRANT REPLICA MONITOR ON *.* TO 'backup'@'localhost';
E guarde a senha num arquivo protegido, não na linha de comando (que aparece no ps para qualquer usuário do servidor):
# /etc/mysql/backup.cnf (chmod 600, dono root)
[client]
user = backup
password = troque-esta-senha
Atenção: use o mariadb-backup da mesma versão do servidor. Ele vem no pacote mariadb-backup da sua distribuição ou do repositório oficial. Uma versão diferente pode não entender o formato dos arquivos.
Backup completo
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup \
--target-dir=/backup/mariadb/full \
--parallel=4
O --parallel copia vários arquivos ao mesmo tempo e pode ajudar conforme o armazenamento e a carga. Backup a quente não significa impacto zero: há consumo de I/O e bloqueios em determinadas etapas. O BACKUP STAGE procura reduzir esse impacto, mas pode bloquear DDL, escritas em tabelas não transacionais e commits. Transações e DDL em andamento também podem fazer o backup esperar; não dá para prometer “poucos instantes” em qualquer ambiente. Confira o comportamento da sua versão de manutenção na documentação oficial.
Dentro do diretório, além dos dados, ficam arquivos de controle. A partir do 11.1 eles têm o prefixo mariadb_backup_ (antes era xtrabackup_, herança da origem da ferramenta):
mariadb_backup_checkpoints: tipo de backup e posição no log. É o que permite os incrementais.mariadb_backup_binlog_info: a posição exata do binlog no momento do backup. Guarde essa informação, porque vamos precisar dela na recuperação até o minuto.
Preparar: o passo que todo mundo esquece
Logo após a cópia, os arquivos ainda não estão consistentes, porque transações aconteceram durante o backup. O passo --prepare aplica o log e deixa tudo pronto para uso:
mariadb-backup --prepare --target-dir=/backup/mariadb/full
Ele não precisa de conexão com o banco e pode rodar em outra máquina. Já vi empresa com meses de backups "feitos" que nunca tinham passado pelo prepare e só descobriu isso na hora do desastre. Não seja essa empresa.
Restaurar
O restore exige o servidor parado e o diretório de dados vazio:
systemctl stop mariadb
# Guarde o diretório atual em vez de apagar (você vai me agradecer)
mv /var/lib/mysql /var/lib/mysql.antes-do-restore
mkdir /var/lib/mysql
# Copia os arquivos preparados de volta
mariadb-backup --copy-back --target-dir=/backup/mariadb/full
# Os arquivos precisam pertencer ao usuário do serviço
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb
Se o disco estiver apertado, --move-back move em vez de copiar, mas aí o backup deixa de existir naquele local. Prefira --copy-back.
Backups incrementais: só o que mudou
Fazer backup completo de 500 GB todo dia gasta disco e tempo. O incremental copia apenas as páginas alteradas desde o backup anterior:
# Domingo: completo
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup --target-dir=/backup/mariadb/full
# Segunda: incremental em relação ao completo
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup --target-dir=/backup/mariadb/inc1 \
--incremental-basedir=/backup/mariadb/full
# Terça: incremental em relação ao de segunda
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup --target-dir=/backup/mariadb/inc2 \
--incremental-basedir=/backup/mariadb/inc1
Na hora de restaurar, prepare uma cópia do completo e aplique os incrementais em ordem sobre essa mesma cópia. Confira os checkpoints: o from_lsn de cada incremental precisa corresponder ao to_lsn da etapa anterior. Crie /restore antes da cópia e garanta que /restore/full ainda não exista:
# Trabalhe sempre numa CÓPIA: o prepare altera o diretório
mkdir -p /restore
test ! -e /restore/full || {
echo "Destino /restore/full já existe; interrompendo." >&2
exit 1
}
cp -a /backup/mariadb/full /restore/full
mariadb-backup --prepare --target-dir=/restore/full
mariadb-backup --prepare --target-dir=/restore/full \
--incremental-dir=/backup/mariadb/inc1
mariadb-backup --prepare --target-dir=/restore/full \
--incremental-dir=/backup/mariadb/inc2
# Depois disso, o restore é igual ao do backup completo
Atenção: pular um incremental ou aplicar fora de ordem invalida a cadeia. Se o inc1 se perder, o inc2 não serve para nada. É por isso que o completo semanal é importante: ele limita o tamanho da corrente.
Restauração até o minuto exato (point-in-time recovery)
O cenário clássico: às 14h37, alguém roda um UPDATE clientes SET ativo = 0 sem WHERE. O último backup é das 2h da manhã. Voltar para as 2h significa perder 12 horas de vendas.
Com o binlog ligado, você restaura o backup e depois reaplica tudo o que aconteceu entre as 2h e as 14h36. O binlog precisa estar ativo antes do incidente:
[mariadb]
log_bin = /var/lib/mysql/mariadb-bin
binlog_format = ROW
expire_logs_days = 8 # mantenha mais dias do que o intervalo entre completos
O passo a passo:
# 1. Restaure o backup (completo + incrementais) como mostrado acima
# 2. Veja onde o binlog estava no momento do backup
cat /restore/full/mariadb_backup_binlog_info
# Antes do 11.1, use: cat /restore/full/xtrabackup_binlog_info
# mariadb-bin.000231 48213 0-1-99812
# 3. Reaplique do ponto do backup até um instante antes do erro
mariadb-binlog --start-position=48213 \
--stop-datetime="2026-11-03 14:36:59" \
/binlogs/mariadb-bin.000231 /binlogs/mariadb-bin.000232 \
| mariadb -u root -p
Atenção: copie os arquivos de binlog para fora do servidor antes de restaurar, porque o diretório de dados será substituído. E repare que o último comando usa um usuário administrativo: o usuário backup não tem permissão para escrever nas tabelas, e é assim que deve ser.
Antes de reaplicar, gere um arquivo e inspecione os eventos. Com binlog_format=ROW, um grep pelo SQL original não é suficiente: use mariadb-binlog --base64-output=DECODE-ROWS -vv para uma cópia de inspeção (não para replay). Confirme os limites das transações e prefira posições exatas de eventos para excluir a transação problemática; --stop-datetime depende do fuso horário e não garante precisão de negócio “até o minuto”. Mantenha a aplicação sem escrita durante o replay e use todos os binlogs necessários, sem lacunas. As coordenadas registradas só têm consistência garantida com o backup quando ele não usa --no-lock.
E se o estrago for em poucas tabelas e você quiser evitar esse tipo de acidente no futuro, vale conhecer as tabelas temporais. Com elas, o próprio MariaDB guarda o histórico das linhas e você consulta "como estava às 14h30" com um SELECT.
Compactar e tirar do servidor
Backup no mesmo disco do banco não é backup: se o disco morrer, vão os dois. O mariadb-backup pode gerar um fluxo único, que você compacta e envia direto para outra máquina:
# Backup compactado direto para outro servidor, sem arquivo intermediário
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup --stream=xbstream --parallel=4 \
| gzip \
| ssh backup@servidor-remoto "cat > /backups/mariadb-$(date +%F).xb.gz"
# Para restaurar: descompactar e extrair, depois --prepare normalmente
mkdir /restore/full
gunzip -c mariadb-2026-11-03.xb.gz | mbstream -x -C /restore/full
Siga a regra 3-2-1: três cópias, em dois tipos de mídia, uma delas fora do local (outro datacenter ou um bucket em nuvem).
Rode o backup na réplica, não no primário
Se você tem replicação, faça o backup a partir da réplica. O primário fica livre para a aplicação:
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup --target-dir=/backup/mariadb/full \
--slave-info --safe-slave-backup
O --slave-info registra a posição do primário, o que permite usar o backup para criar uma réplica nova. O --safe-slave-backup coordena a parada da thread SQL da réplica e espera não haver tabelas temporárias abertas, podendo repetir esse processo até atingir o limite de espera. Não é uma pausa garantidamente restrita aos instantes finais. Se você ainda não tem réplica, o artigo sobre o Galera Cluster mostra uma opção de alta disponibilidade que já vem na caixa.
Script pronto para agendar
Este exemplo faz um backup completo no primeiro disparo de cada semana e incrementais nos demais. Uma trava evita execuções simultâneas e o diretório só recebe o nome definitivo depois de a ferramenta terminar com sucesso. A retenção automática fica fora do exemplo: nunca remova um completo separado dos incrementais que dependem dele.
#!/usr/bin/env bash
# /usr/local/bin/backup-mariadb.sh
set -euo pipefail
export LC_ALL=C
umask 077
BASE=/backup/mariadb
CRED=--defaults-extra-file=/etc/mysql/backup.cnf
mkdir -p "$BASE"
exec 9>"$BASE/.backup.lock"
flock -n 9 || { echo "Backup já está em execução." >&2; exit 1; }
SEMANA="$BASE/$(date +%G-%V)"
mkdir -p "$SEMANA"
PENDENTE=$(mktemp -d "$SEMANA/.pending-XXXXXXXX")
trap 'echo "Falha: confira o diretório parcial $PENDENTE; ele não será usado como base." >&2' ERR
if [[ ! -d "$SEMANA/full" ]]; then
mariadb-backup "$CRED" --backup --target-dir="$PENDENTE" --parallel=4
mv -T -- "$PENDENTE" "$SEMANA/full"
else
shopt -s nullglob
incrementais=( "$SEMANA"/inc-* )
shopt -u nullglob
ULTIMO="$SEMANA/full"
for candidato in "${incrementais[@]}"; do
[[ -d "$candidato" ]] || continue
if [[ "$ULTIMO" == "$SEMANA/full" || "$candidato" > "$ULTIMO" ]]; then
ULTIMO="$candidato"
fi
done
DESTINO="$SEMANA/inc-$(date +%Y%m%d-%H%M%S)-$"
mariadb-backup "$CRED" --backup --target-dir="$PENDENTE" \
--incremental-basedir="$ULTIMO" --parallel=4
mv -T -- "$PENDENTE" "$DESTINO"
fi
# Retenção: remova somente cadeias semanais inteiras e concluídas,
# após validar cópias externas e os binlogs exigidos pela sua política.
# A data de modificação do diretório não representa a idade da cadeia.
Agende no cron do root, por exemplo às 2h:
0 2 * * * /usr/local/bin/backup-mariadb.sh >> /var/log/backup-mariadb.log 2>&1
O exemplo pressupõe Linux com Bash, flock e GNU mv. Um diretório definitivo indica apenas que o comando terminou: isso não substitui preparar e restaurar o backup em teste. Resultados parciais ficam com prefixo .pending- e exigem inspeção.
Adapte o script para enviar o resultado para fora do servidor e para avisar quando falhar, seja por e-mail, Slack ou o seu monitoramento. Um backup que falha em silêncio é pior do que não ter backup, porque dá falsa segurança.
Backup que nunca foi restaurado é só uma esperança
Este é o ponto mais importante do artigo inteiro. Uma vez por mês, no mínimo:
- Restaure o último backup (completo + incrementais) em um servidor de teste.
- Suba o MariaDB e confira o log de erro.
- Valide os dados: contagem de linhas das tabelas principais e um
CHECK TABLEnas mais críticas. - Cronometre. O tempo de restore é o seu RTO real, aquele que você vai responder quando perguntarem "em quanto tempo voltamos?".
- Ensaie o point-in-time pelo menos uma vez, com um erro simulado.
O que isso significa para você
- Base pequena e pouco crítica:
mariadb-dump --single-transactiondiário, fora do servidor, e restore testado. - Base que cresce ou que não pode ficar fora do ar:
mariadb-backupcom completo semanal, incrementais diários, binlog ligado e cópia fora do local. - Qualquer cenário: teste o restore. Todo mês.
Tudo isso é nativo e gratuito no MariaDB Community, sem licença extra e sem ferramenta de terceiros. É um daqueles recursos que só aparecem de verdade no pior dia do ano, e nesse dia fazem toda a diferença.
E antes de criar índice novo ou mexer em configuração para resolver lentidão, garanta que este backup está em dia. Falo da parte de desempenho no artigo MariaDB lento? Como achar e corrigir as consultas que estão derrubando seu sistema.
Leia também
- MariaDB: tabelas temporais (System Versioning) para viajar no tempo dos dados
- MariaDB Galera Cluster: multi-master síncrono que já vem na caixa
- MariaDB 10.6 chegou ao fim de vida: para qual versão migrar?
- Migrar do MySQL para o MariaDB: o guia que eu queria ter lido antes
- Até onde o MariaDB aguenta? As perguntas que todo CTO faz
- MariaDB Spider: sharding transparente com tabelas distribuídas
Todos os artigos sobre MariaDB →
Referências oficiais
Nota técnica: os exemplos foram revisados com a documentação, mas não executados em um servidor MariaDB nesta publicação. Valide versão, caminhos, privilégios e comportamento em laboratório antes de usar em produção. As capas são ilustrações fotográficas geradas por IA, não resultados de testes.
Continue essa conversa
Quer trocar experiências sobre MariaDB? Entre para a Comunidade MariaDB Brasil.
Me acompanhe no LinkedIn e no Instagram. Para fortalecer sua base em SQL, conheça meu livro Aprendendo MySQL. Conheça também a HTI Tecnologia.