Por que cp não serve para fazer backup do SQLite
Um banco SQLite é um único arquivo, então é tentador fazer backup com uma cópia simples. Às vezes funciona. Boa parte das vezes, não.
Dois problemas podem aparecer:
- Outra conexão está no meio de uma escrita quando você copia. O arquivo de destino fica com uma transação aplicada pela metade — corrompido logo na abertura.
- O banco está em modo WAL (o padrão na maioria das aplicações modernas). As mudanças mais recentes ficam num arquivo separado, o
database.db-wal. Se você copiar só o arquivo principal, perde dados sem perceber.
O SQLite oferece ferramentas próprias para isso. Elas lidam com locks, conteúdo do WAL e escritas concorrentes sem pegadinhas. Use elas no lugar do cp.
O comando .backup do SQLite
A forma mais rápida de fazer backup de um banco SQLite pelo CLI é o dot command .backup:
sqlite3 app.db
sqlite> .backup backup.db
sqlite> .quit
Isso copia o app.db inteirinho para o backup.db. E funciona mesmo se outros processos estiverem lendo ou escrevendo no banco — a API de backup do SQLite pega vários cadeados pequenos em vez de um único cadeado gigante, copia as páginas de forma incremental e refaz aquelas que forem modificadas durante o processo.
O resultado é um banco SQLite totalmente funcional. Você abre ele como qualquer outro:
sqlite3 backup.db
sqlite> .tables
Dá pra fazer tudo em um único comando no shell, que é como a maioria dos cron jobs acaba ficando:
sqlite3 app.db ".backup '/var/backups/app-$(date +%Y%m%d).db'"
Um arquivo entra, outro arquivo sai. Sem ciclo de dump/restore, sem parsing de SQL — apenas páginas copiadas direto na camada de armazenamento.
VACUUM INTO: uma cópia compactada do banco
O VACUUM INTO é um recurso parecido, mas com um propósito diferente. Ele gera uma cópia limpa e reconstruída do banco em um novo arquivo:
O resultado é o mesmo banco lógico, só que reescrito do zero — cada página compactada ao máximo, sem fragmentação e sem páginas livres sobrando de linhas que foram apagadas. Isso deixa o arquivo de backup no menor tamanho possível.
Quando usar cada um:
.backup— para backups rotineiros e frequentes. É mais rápido, convive bem com escritas concorrentes e gera uma cópia fiel byte a byte.VACUUM INTO— para snapshots periódicos quando você também quer um arquivo enxuto e do menor tamanho possível. É mais lento porque reescreve tudo, e segura um lock de escrita na origem durante o processo.
Os dois geram um arquivo .db válido, pronto para abrir na hora.
A API de backup online a partir do código da aplicação
Dentro de uma aplicação, você não fica chamando o sqlite3 por linha de comando. Você usa a API de backup online que o seu driver expõe. No módulo sqlite3 da stdlib do Python, isso é o Connection.backup:
import sqlite3
source = sqlite3.connect("app.db")
dest = sqlite3.connect("backup.db")
with dest:
source.backup(dest)
source.close()
dest.close()
O método backup copia páginas de source para dest enquanto outras conexões seguem trabalhando normalmente. Você também pode passar pages= para copiar em blocos e progress= para receber um callback — útil em bancos grandes, quando você quer controlar o ritmo da cópia ou exibir o progresso.
A maioria dos drivers em outras linguagens expõe a mesma API em C (sqlite3_backup_init, _step, _finish) com nomes parecidos. O fluxo é sempre o mesmo: abre a origem, abre o destino, percorre as páginas e finaliza.
Backup do SQLite com o banco em uso
É aqui que o SQLite brilha discretamente. Tanto o .backup quanto a online backup API foram desenhados para hot backups — o banco de origem pode permanecer aberto e ativo o tempo inteiro.
O que acontece nos bastidores:
- O backup adquire um shared lock e começa a copiar as páginas.
- Se algum writer modificar uma página que ainda não foi copiada, o backup percebe e relê essa página.
- A cópia termina quando todas as páginas estão consistentes.
Não é preciso parar a aplicação, derrubar conexões ou agendar uma janela de manutenção. Em um banco com muito tráfego, o backup pode precisar de alguns ciclos extras para convergir, mas ele converge. O arquivo de destino que você obtém no fim representa um snapshot consistente daquele ponto no tempo.
Um detalhe importante: se você estiver usando o modo WAL, rode PRAGMA wal_checkpoint(TRUNCATE); de vez em quando para evitar que o arquivo WAL cresça sem limite. O backup em si lida com o WAL corretamente — isso é só uma questão de higiene geral do WAL.
Como restaurar um banco SQLite
Restaurar um banco SQLite é incrivelmente sem graça, e essa é justamente a ideia. O arquivo de backup é o próprio banco. Para usá-lo, basta abrir:
sqlite3 backup.db
sqlite> SELECT COUNT(*) FROM notes;
Para restaurar por cima de um banco em uso — por exemplo, depois de uma perda de dados — o caminho seguro é este:
- Pare todo processo que esteja com o banco aberto.
- Apague os arquivos
app.db,app.db-waleapp.db-shmexistentes. Sobras de WAL/SHM do banco antigo vão confundir o SQLite quando combinadas com o arquivo principal restaurado. - Coloque o backup no lugar:
cp backup.db app.db. - Suba sua aplicação de novo.
Os arquivos -wal e -shm fazem diferença. Se você pular o passo 2, o SQLite pode tentar aplicar um WAL desatualizado em cima do arquivo principal restaurado, e o resultado é corrupção ou dados misturados de um jeito estranho.
Dentro da CLI também existe o comando .restore, que é o espelho do .backup:
sqlite3 app.db
sqlite> .restore backup.db
sqlite> .quit
Isso sobrescreve o conteúdo do banco conectado com o conteúdo de backup.db. Por baixo dos panos, é a mesma online backup API, só que no sentido inverso.
.dump é outra história
Você vai encontrar menções ao .dump em tutoriais mais antigos. Ele não é um backup no mesmo sentido — o que ele gera é um arquivo de texto SQL cheio de comandos CREATE e INSERT:
sqlite3 app.db .dump > app.sql
Para restaurar, basta executar o SQL novamente:
sqlite3 new.db < app.sql
Isso é útil pra migrar entre versões do SQLite, comparar schemas no git ou levar os dados pra outro banco. É mais lento, ocupa mais espaço e perde algumas coisas em relação ao .backup (collations customizadas, colunas geradas e alguns pragmas podem exigir atenção extra). Pra um backup de verdade de um banco em produção, prefira .backup ou VACUUM INTO.
Uma rotina de backup que funciona
Pra maioria das aplicações, essa combinação dá conta do recado:
- Um
.backupagendado — de hora em hora, diariamente, o que a sua tolerância a perda de dados permitir. Barato, rápido e a quente. - Um
VACUUM INTOsemanal pra um caminho separado. Pega eventuais inconsistências, gera um snapshot compactado e exercita um caminho de código diferente. - Uma política de retenção: guarde os últimos N backups diários e os últimos M semanais. Bancos SQLite comprimem bem, então rodar
gzip backup.dbdepois compensa. - De vez em quando, restaure um deles e rode algumas queries pra conferir. Backup que nunca foi testado é torcida, não é backup.
# Diariamente, no cron:
sqlite3 /var/lib/app/app.db ".backup '/var/backups/app-$(date +%F).db'"
gzip "/var/backups/app-$(date +%F).db"
# Semanalmente:
sqlite3 /var/lib/app/app.db "VACUUM INTO '/var/backups/app-weekly-$(date +%F).db'"
Os dois comandos podem ser executados com tranquilidade enquanto a aplicação continua atendendo requisições.
A seguir: configurações com PRAGMA
Backup é só uma das preocupações operacionais; ajustar o comportamento em tempo de execução é outra. O SQLite expõe seus parâmetros através de instruções PRAGMA — modo de journal, nível synchronous, tamanho do cache, verificação de foreign keys. Na próxima página, vamos passar pelos que realmente vale a pena conhecer.
Perguntas frequentes
Como faço backup de um banco SQLite?
Pelo CLI, conecte no banco de origem e rode .backup caminho/para/backup.db. No código da aplicação, use a API de backup online (sqlite3_backup_init em C, ou o equivalente no driver da sua linguagem). Os dois jeitos geram uma cópia consistente mesmo com outras conexões escrevendo no banco.
Posso só copiar o arquivo .db como backup?
Só se você tiver certeza de que nenhum processo está com o banco aberto para escrita. Caso contrário, dá pra copiar o arquivo no meio de uma transação e acabar com um backup corrompido — ou perder dados que ainda estão no arquivo WAL. Use .backup ou VACUUM INTO, que tratam o lock e o conteúdo do WAL corretamente.
Qual a diferença entre .backup e VACUUM INTO?
O .backup usa a API de backup online e gera uma cópia fiel byte a byte, incluindo páginas não utilizadas. Já o VACUUM INTO 'arquivo.db' escreve uma cópia recém-compactada — menor e desfragmentada, mas reescreve cada página. Use .backup para backups de rotina e VACUUM INTO quando também quiser recuperar espaço.
Como restaurar um banco SQLite a partir de um arquivo de backup?
Se o backup é um arquivo .db, é só abrir — bancos SQLite são arquivos únicos. Para restaurar por cima de um banco existente, pare a aplicação, substitua o arquivo (e apague qualquer -wal/-shm que tiver sobrado) e abra de novo. Pelo CLI, dá também para rodar .restore caminho/para/backup.db conectado a um banco vazio.