Boas PráticasMay 18, 202612 min de leitura

Boas Práticas de Design de Schema de Banco de Dados para 2026

Bons schemas de banco de dados são a fundação de software confiável e escalável. Este guia cobre 10 boas práticas concretas — de convenções de nomenclatura e normalização a estratégia de índices e soft deletes — com exemplos que você pode aplicar imediatamente.

Um schema de banco de dados bem projetado é uma das decisões de maior alavancagem em qualquer projeto de software. O schema define como os dados são armazenados, relacionados e recuperados — e ao contrário do código da aplicação, é caro de mudar uma vez que os dados estejam em produção. Acertar o design desde o início economiza meses de migrations dolorosas e depuração de performance depois.

Aqui estão dez boas práticas concretas para design de schema de banco de dados que se aplicam a todo banco de dados relacional — PostgreSQL, MySQL, SQLite e além.

1. Use Convenções de Nomenclatura Consistentes e Descritivas

Escolha uma convenção e mantenha-a em todo o schema. O padrão mais amplamente aceito para bancos de dados relacionais é snake_case: user_id, created_at, order_status. Use nomes de tabelas no singular (user, order, product) em vez do plural (users, orders, products) — isso corresponde à forma como você pensa sobre uma única linha: "uma linha user tem um email." Nomeie colunas de chave estrangeira após a tabela que referenciam seguida de _id: user_id, category_id, parent_post_id.

2. Normalize para Terceira Forma Normal — Depois Desnormalize Deliberadamente

Comece todo design na Terceira Forma Normal (3FN): cada coluna depende da chave primária, toda a chave primária e nada além da chave primária. Isso elimina redundância e previne anomalias de atualização. Não armazene o nome de um cliente na tabela orders quando você já o tem em users. Só desnormalize — por exemplo, cacheando um total_amount calculado em um pedido — quando tiver um problema de performance medido, e documente o porquê.

3. Escolha a Estratégia de Chave Primária Certa

Para a maioria das tabelas, um inteiro auto-incrementado (SERIAL / BIGSERIAL no PostgreSQL, AUTO_INCREMENT no MySQL) é a chave primária mais simples e eficiente. É compacto, rápido para indexar e previsível. Use BIGINT em vez de INT se a tabela crescer além de 2 bilhões de linhas.

UUIDs (UUID / CHAR(36)) são valiosos quando você precisa de IDs globalmente únicos — para sistemas distribuídos, APIs públicas onde você não quer expor IDs sequenciais, ou quando os registros são criados em múltiplos serviços. O trade-off é tamanho de índice maior e performance de insert ligeiramente pior em índices clusterizados. Um meio-termo prático: use uma variante UUID sequencial (uuid_generate_v7() no PostgreSQL 17+) que preserva a ordem de insert enquanto permanece globalmente único.

4. Sempre Defina Restrições de Chave Estrangeira

Nunca confie apenas no código da aplicação para manter a integridade referencial. Defina restrições FOREIGN KEY no banco de dados para que o motor em si imponha os relacionamentos. Escolha o comportamento ON DELETE e ON UPDATE explicitamente: RESTRICT (o padrão seguro), CASCADE para dados filhos verdadeiramente dependentes ou SET NULL quando um filho pode existir sem seu pai. Restrições FK ausentes são uma causa principal de dados órfãos — linhas que referenciam linhas pai excluídas e causam bugs silenciosos.

5. Indexe Chaves Estrangeiras e Predicados de Consulta

Toda coluna de chave estrangeira deve ter um índice — sem um, operações JOIN e varreduras ON DELETE CASCADE realizarão varreduras completas de tabela. Além de chaves estrangeiras, adicione índices para qualquer coluna que apareça frequentemente em cláusulas WHERE, cláusulas ORDER BY ou agregações GROUP BY. No entanto, não indexe tudo: cada índice adiciona sobrecarga às operações INSERT, UPDATE e DELETE. Faça profile antes de adicionar e remova índices que não estão sendo usados.

6. Trate NULL com Intenção

NULL significa "desconhecido" — não zero, não string vazia, não falso. Use NOT NULL como padrão para cada coluna a menos que tenha uma razão genuína para um valor estar ausente. Um erro comum é tornar colunas nullable simplesmente porque o valor pode não ser definido imediatamente. Em vez disso, considere usar um valor padrão (DEFAULT '', DEFAULT 0, DEFAULT now()) e atualizá-lo depois. Reserve NULL para dados verdadeiramente opcionais onde a ausência de um valor carrega significado semântico, como deleted_at em um padrão de soft delete.

7. Implemente Soft Deletes com deleted_at

Hard deletes (DELETE FROM users WHERE id = 1) removem permanentemente dados e podem causar referências órfãs e problemas de auditoria. Um padrão de soft delete adiciona uma coluna nullable deleted_at TIMESTAMP. Excluir um registro define deleted_at = now() em vez de remover a linha. Todas as consultas filtram com WHERE deleted_at IS NULL. Isso preserva o histórico, habilita restauração e simplifica trilhas de auditoria. Use um índice parcial em deleted_at IS NULL para manter a performance de consultas rápida em tabelas grandes.

8. Adicione created_at e updated_at a Cada Tabela

Cada tabela deve ter created_at TIMESTAMP NOT NULL DEFAULT now() e updated_at TIMESTAMP NOT NULL DEFAULT now(). Essas duas colunas custam quase nada e fornecem valor enorme: depurar problemas de dados, entender padrões de uso, sincronizar dados com sistemas externos e habilitar consultas baseadas em tempo. Na maioria dos frameworks (Laravel, Rails, Django), são adicionadas automaticamente pelas convenções do ORM. Não lute contra a convenção — abrace-a.

9. Defina Índices para Restrições de Negócio Únicas

As regras de negócio frequentemente requerem unicidade que vai além de uma única chave primária. Uma tabela users deve ter um índice UNIQUE em email. Uma tabela slugs deve ter um índice UNIQUE em (slug, locale). Uma tabela de junção user_roles deve ter um índice UNIQUE em (user_id, role_id). Defina essas restrições no banco de dados — não apenas no código de validação — para que o banco de dados seja a última linha de defesa contra dados duplicados independentemente de qual aplicação ou script escreva nele.

10. Documente Seu Schema

Um schema sem documentação é uma API não documentada. Use comentários de colunas, ferramentas ERD ou um README de schema para explicar decisões não óbvias: por que uma coluna é nullable, quais são os valores válidos para um enum de status, por que um índice específico existe. ER Flow gera um documento visual vivo do seu schema automaticamente — cada tabela, coluna e relacionamento é visível em um canvas compartilhável que fica sincronizado com seu banco de dados através de migrations controladas por versão. Trate o diagrama ER como documentação que todo o seu time pode ler.

Aplicando Estas Práticas

Bom design de schema é um hábito. Revise seu próximo schema contra este checklist antes de escrever qualquer código de migration. Identifique colunas nullable que deveriam ser NOT NULL. Verifique se cada chave estrangeira tem uma restrição e um índice. Confirme que created_at e updated_at estão presentes. Verifique se regras de negócio únicas são impostas no nível do banco de dados. Pequena disciplina aplicada de forma consistente produz schemas que são um prazer de trabalhar anos depois que foram projetados pela primeira vez.