Menu

Migrations no SQLite com PRAGMA user_version

Aprenda a evoluir o schema do SQLite com segurança usando PRAGMA user_version, scripts de migration ordenados e transações para manter tudo reversível.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

Schemas mudam. Planeje pensando nisso.

A primeira versão do seu schema nunca é a última. Colunas vão sendo adicionadas, tabelas acabam sendo divididas, índices precisam ser repensados. A questão não é se o seu schema vai mudar — é se essa mudança vai chegar de forma limpa em todo notebook, servidor e dispositivo de usuário que já tem uma cópia mais antiga do banco.

É exatamente para isso que servem as migrations: uma sequência de scripts pequenos e ordenados que levam o banco da versão N para a N+1. Rode na ordem certa e qualquer banco se atualiza até a versão atual. Se você abrir mão dessa disciplina, vai cair naquele clássico "na minha máquina funciona" e perder a tarde inteira investigando o problema.

Para fazer esse versionamento de schema no SQLite, você tem uma única ferramenta nativa: PRAGMA user_version. É um inteiro de 32 bits que o banco guarda para você, e que o próprio SQLite nunca toca. O significado dele é você quem define.

Um banco recém-criado começa em 0. Defina esse valor para o número da última migration que você aplicou. Na inicialização, basta ler esse número para saber em que ponto o schema está.

Um loop mínimo de migrations

O modelo mental é simples: cada migration é um script SQL numerado. Sua aplicação lê o user_version atual, executa em ordem todos os scripts com número maior e atualiza o user_version ao final de cada um.

Veja a migration 1, que cria o schema inicial:

Repare em duas coisas. Tudo está envolvido por BEGIN; ... COMMIT;, ou seja, é atômico — se o CREATE TABLE falhar, o user_version não avança e você pode corrigir e rodar de novo. E o PRAGMA user_version = 1 é a última instrução antes do commit, então a versão só muda se todo o resto deu certo.

Agora imagine que você precisa adicionar uma coluna created_at. Essa é a migration 2:

Um banco na versão 0 roda os dois. Um banco na versão 1 roda só o segundo. Um banco na versão 2 não roda nada. A ordem é o contrato.

O que dá e o que não dá fazer com ALTER TABLE

O ALTER TABLE do SQLite é propositalmente limitado. Ele aceita apenas:

  • ADD COLUMN — adiciona uma nova coluna, com valor padrão opcional.
  • DROP COLUMN — remove uma coluna (a partir da versão 3.35).
  • RENAME COLUMN — renomeia uma coluna (a partir da 3.25).
  • RENAME TO — renomeia a própria tabela.

E é só isso. Não dá pra mudar o tipo de uma coluna, alterar um NOT NULL, mexer numa restrição CHECK nem adicionar uma FOREIGN KEY numa coluna que já existe.

-- Não suportado:
ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(255);
ALTER TABLE users ADD CONSTRAINT users_email_check CHECK (email LIKE '%@%');

Quando você precisa fazer uma alteração que o SQLite não suporta diretamente, a receita oficial é "reconstruir a tabela". É mais verboso, mas funciona sempre.

Reconstruindo a tabela para mudanças maiores

O padrão é o seguinte: crie uma nova tabela com o formato desejado, copie os dados, descarte a antiga e renomeie a nova no lugar dela. Tudo dentro de uma transação.

A documentação oficial do SQLite chama isso de 12-step recipe (receita de 12 passos) e ainda traz alertas extras sobre triggers, views e referências de chave estrangeira — vale a pena ler com calma antes de aplicar num schema em produção. Para a maioria dos casos, a versão de quatro passos acima já dá conta.

Um aviso importante: se existirem chaves estrangeiras apontando para a tabela que você está reconstruindo, execute PRAGMA foreign_keys = OFF antes da migration e PRAGMA foreign_keys = ON depois. Caso contrário, o DROP TABLE pode quebrar a integridade referencial no meio do caminho.

Rodando migrations sqlite a partir da sua aplicação

O controle de versão é simples o bastante para você escrever na mão. Em Python, usando só a biblioteca padrão:

Os princípios fundamentais:

  • As migrations são numeradas em sequência, começando do 1. Sem buracos, sem reordenação.
  • Cada migration roda dentro de uma transação, junto com o PRAGMA user_version = N que incrementa a versão.
  • Depois que uma migration foi commitada e entrou em produção, ela é intocável. Mudança nova vira migration nova.

Essa última regra é justamente a que mais cai por terra nos times. Se você editar a migration 3 depois que o banco de um colega já rodou ela, o banco dele fica silenciosamente fora de sincronia com o seu — e para sempre.

Mantendo um histórico de auditoria

O PRAGMA user_version te diz em que ponto o banco está, mas não te conta quando cada passo rodou nem o que ele fez. Uma tabelinha simples de controle resolve isso:

Agora você tem uma linha por migration, com nome e timestamp — útil pra hora de debugar aquele clássico "por que esse banco tem uma coluna que o código não espera?"

O PRAGMA user_version continua sendo a fonte da verdade pro loop; a tabela existe pros humanos.

Rollback de migration no SQLite: o que a transação resolve (e o que não resolve)

O DDL do SQLite é transacional. Se a migration 5 começa criando uma tabela, copiando dados e atualizando o user_version, e a cópia falha no meio do caminho, o ROLLBACK desfaz tudo — inclusive o CREATE TABLE. O banco volta exatamente ao estado anterior ao BEGIN.

Isso cobre as migrations que falharam. Mas não resolve o caso das migrations que foram comitadas com sucesso e das quais você se arrependeu depois. Para essas, você escreve uma down-migration separada — um script que desfaz a mudança. O SQLite não tem reversão automática. Se a migration 7 adicionou uma coluna, a versão "down" dropa essa coluna. Se a migration 7 dropou uma coluna, a versão "down" não consegue recuperar os dados; o máximo que dá pra fazer é recriar a coluna vazia.

Na prática, muitos projetos pequenos ignoram down-migrations e dependem de backups como mecanismo de "desfazer". É uma escolha válida — desde que você realmente faça os backups.

Alguns hábitos que evitam dor de cabeça depois

  • Uma migration por mudança lógica. Uma migration que adiciona três colunas sem relação entre si é mais difícil de revisar e de reverter do que três migrations separadas.
  • Teste migrations em uma cópia de produção. Mudanças de schema podem ser lentas em tabelas grandes; descobrir isso direto em produção não é nada legal.
  • Nunca edite uma migration que já foi publicada. Crie uma nova.
  • Faça backup antes. Um .backup rápido no CLI ou uma cópia do arquivo com o banco fechado é um seguro barato antes de qualquer migration não-trivial.
  • Cuidado com o PRAGMA foreign_keys. Desligue durante reconstruções de tabela e religue depois.

Em projetos maiores, vale usar uma ferramenta dedicada — Alembic com SQLAlchemy, golang-migrate, Knex, Flyway. Elas cuidam da ordenação, de execuções concorrentes e de convenções de time que, do contrário, você reinventaria do zero. Os princípios são os mesmos do loop que mostramos acima; a ferramenta só elimina o boilerplate.

A seguir: modo WAL e concorrência

Migrations normalmente rodam com a aplicação offline ou segurando um lock exclusivo. No resto do tempo, seu banco está atendendo leituras e escritas de várias conexões ao mesmo tempo — e o journal mode padrão do SQLite nem sempre é a melhor opção. A próxima página fala sobre o modo WAL, o que ele muda e quando vale a pena migrar para ele.

Perguntas frequentes

Como versionar o schema de um banco SQLite?

O SQLite já oferece um inteiro de 32 bits por banco chamado user_version, acessado via PRAGMA user_version. Na inicialização da aplicação, leia esse valor, compare com o número da última migration que o seu código conhece e rode em ordem o que estiver faltando. Não precisa de tabela extra — embora muita gente crie uma só para ter histórico de auditoria.

Dá para fazer rollback de uma migration no SQLite?

Envolva cada migration em BEGIN; ... COMMIT;. Se algo falhar no meio, o ROLLBACK desfaz tudo — tanto mudanças de schema quanto de dados, já que o DDL do SQLite é transacional. Agora, para reverter uma migration que já foi commitada, você precisa de um script de "down" escrito por você mesmo: o SQLite não gera isso automaticamente.

Por que o ALTER TABLE do SQLite é tão limitado?

O SQLite suporta ALTER TABLE ADD COLUMN, RENAME TABLE, RENAME COLUMN e DROP COLUMN, mas não permite mudanças arbitrárias como alterar o tipo ou as constraints de uma coluna. A saída é a famosa receita de 12 passos: criar uma tabela nova com o formato desejado, fazer INSERT INTO new_table SELECT ... FROM old_table, dropar a antiga e renomear a nova.

Vale a pena usar uma ferramenta de migration ou escrever a minha?

Para projetos pequenos, um loop caseiro lendo arquivos .sql numerados, controlado por PRAGMA user_version, dá uns 30 linhas de código e resolve numa boa. Já em projetos maiores, ferramentas como Alembic (Python), golang-migrate (Go) ou Knex (Node) cuidam de ordenação, locks e fluxo em equipe — coisas que você acabaria reinventando do zero.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR