Designer de Esquema de Banco de Dados
Projete esquemas de banco de dados prontos para produção com boas práticas integradas.
Início Rápido
Apenas descreva seu modelo de dados:
design a schema for an e-commerce platform with users, products, orders
Você receberá um esquema SQL completo como:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total DECIMAL(10,2) NOT NULL,
INDEX idx_orders_user (user_id)
);
O que incluir em sua solicitação:
- Entidades (usuários, produtos, pedidos)
- Relacionamentos-chave (usuários têm pedidos, pedidos têm itens)
- Dicas de escala (alto tráfego, milhões de registros)
- Preferência de banco de dados (SQL/NoSQL) - padrão é SQL se não especificado
Gatilhos
| Gatilho |
Exemplo |
design schema |
"design a schema for user authentication" |
database design |
"database design for multi-tenant SaaS" |
create tables |
"create tables for a blog system" |
schema for |
"schema for inventory management" |
model data |
"model data for real-time analytics" |
I need a database |
"I need a database for tracking orders" |
design NoSQL |
"design NoSQL schema for product catalog" |
Termos-Chave
| Termo |
Definição |
| Normalização |
Organizar dados para reduzir redundância (1FN → 2FN → 3FN) |
| 3FN |
Terceira Forma Normal - sem dependências transitivas entre colunas |
| OLTP |
Online Transaction Processing - muitas escritas, precisa normalização |
| OLAP |
Online Analytical Processing - muitas leituras, beneficia-se de desnormalização |
| Chave Estrangeira (FK) |
Coluna que referencia a chave primária de outra tabela |
| Índice |
Estrutura de dados que acelera consultas (ao custo de escritas mais lentas) |
| Padrão de Acesso |
Como sua aplicação lê/escreve dados (consultas, joins, filtros) |
| Desnormalização |
Duplicar dados intencionalmente para acelerar leituras |
Referência Rápida
| Tarefa |
Abordagem |
Consideração-Chave |
| Novo esquema |
Normalize para 3FN primeiro |
Modelagem de domínio sobre UI |
| SQL vs NoSQL |
Padrões de acesso decidem |
Razão leitura/escrita importa |
| Chaves primárias |
INT ou UUID |
UUID para sistemas distribuídos |
| Chaves estrangeiras |
Sempre restringir |
Estratégia ON DELETE crítica |
| Índices |
FKs + colunas WHERE |
Ordem das colunas importa |
| Migrações |
Sempre reversíveis |
Compatível com versão anterior primeiro |
Visão Geral do Processo
Seus Requisitos de Dados
|
v
+-----------------------------------------------------+
| Fase 1: ANÁLISE |
| * Identificar entidades e relacionamentos |
| * Determinar padrões de acesso (leitura vs escrita) |
| * Escolher SQL ou NoSQL baseado em requisitos |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Fase 2: DESIGN |
| * Normalizar para 3FN (SQL) ou embed/reference |
| * Definir chaves primárias e estrangeiras |
| * Escolher tipos de dados apropriados |
| * Adicionar restrições (UNIQUE, CHECK, NOT NULL) |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Fase 3: OTIMIZAR |
| * Planejar estratégia de indexação |
| * Considerar desnormalização para consultas pesadas |
| * Adicionar timestamps (created_at, updated_at) |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Fase 4: MIGRAR |
| * Gerar scripts de migração (up + down) |
| * Garantir compatibilidade com versão anterior |
| * Planejar deployment sem tempo de inatividade |
+-----------------------------------------------------+
|
v
Esquema Pronto para Produção
Comandos
| Comando |
Quando Usar |
Ação |
design schema for {domain} |
Começando do zero |
Geração completa de esquema |
normalize {table} |
Corrigindo tabela existente |
Aplicar regras de normalização |
add indexes for {table} |
Problemas de desempenho |
Gerar estratégia de indexação |
migration for {change} |
Evolução de esquema |
Criar migração reversível |
review schema |
Code review |
Auditar esquema existente |
Workflow: Comece com design schema → itere com normalize → otimize com add indexes → evolua com migration
Princípios Centrais
| Princípio |
POR QUÊ |
Implementação |
| Modelar o Domínio |
UI muda, domínio não |
Nomes de entidades refletem conceitos do negócio |
| Integridade de Dados Primeiro |
Corrupção é custosa de corrigir |
Restrições no nível de banco de dados |
| Otimizar para Padrão de Acesso |
Não dá para otimizar para ambos |
OLTP: normalizado, OLAP: desnormalizado |
| Planejar para Escala |
Retrofit é doloroso |
Estratégia de índice + plano de particionamento |
Anti-Padrões
| Evitar |
Por Quê |
Instead |
| VARCHAR(255) em tudo |
Desperdiça armazenamento, esconde intenção |
Dimensionar apropriadamente por campo |
| FLOAT para dinheiro |
Erros de arredondamento |
DECIMAL(10,2) |
| Sem restrições de FK |
Dados órfãos |
Sempre definir chaves estrangeiras |
| Sem índices em FKs |
JOINs lentos |
Indexar toda chave estrangeira |
| Armazenar datas como strings |
Não consegue comparar/ordenar |
Tipos DATE, TIMESTAMP |
| SELECT * em consultas |
Busca dados desnecessários |
Listas de coluna explícitas |
| Migrações não reversíveis |
Não consegue reverter |
Sempre escrever migração DOWN |
| Adicionar NOT NULL sem default |
Quebra linhas existentes |
Adicionar nullable, backfill, depois restringir |
Checklist de Verificação
Após projetar um esquema:
Formas Normais
| Forma |
Regra |
Exemplo de Violação |
| 1FN |
Valores atômicos, sem grupos repetidos |
product_ids = '1,2,3' |
| 2FN |
1FN + sem dependências parciais |
customer_name em order_items |
| 3FN |
2FN + sem dependências transitivas |
country derivado de postal_code |
Primeira Forma Normal (1FN)
-- RUIM: Múltiplos valores em coluna
CREATE TABLE orders (
id INT PRIMARY KEY,
product_ids VARCHAR(255) -- '101,102,103'
);
-- BOM: Tabela separada para itens
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT REFERENCES orders(id),
product_id INT
);
Segunda Forma Normal (2FN)
-- RUIM: customer_name depende apenas de customer_id
CREATE TABLE order_items (
order_id INT,
product_id INT,
customer_name VARCHAR(100), -- Dependência parcial!
PRIMARY KEY (order_id, product_id)
);
-- BOM: Dados do cliente em tabela separada
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100)
);
Terceira Forma Normal (3FN)
-- RUIM: country depende de postal_code
CREATE TABLE customers (
id INT PRIMARY KEY,
postal_code VARCHAR(10),
country VARCHAR(50) -- Dependência transitiva!
);
-- BOM: Tabela separada de postal_codes
CREATE TABLE postal_codes (
code VARCHAR(10) PRIMARY KEY,
country VARCHAR(50)
);
Quando Desnormalizar
| Cenário |
Estratégia de Desnormalização |
| Relatórios com muitas leituras |
Agregados pré-calculados |
| JOINs caros |
Colunas derivadas em cache |
| Dashboards de analytics |
Visualizações materializadas |
-- Desnormalizado para desempenho
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
total_amount DECIMAL(10,2), -- Calculado
item_count INT -- Calculado
);
Tipos String
| Tipo |
Caso de Uso |
Exemplo |
| CHAR(n) |
Comprimento fixo |
Códigos de estado, datas ISO |
| VARCHAR(n) |
Comprimento variável |
Nomes, emails |
| TEXT |
Conteúdo longo |
Artigos, descrições |
-- Bom dimensionamento
email VARCHAR(255)
phone VARCHAR(20)
country_code CHAR(2)
Tipos Numéricos
| Tipo |
Intervalo |
Caso de Uso |
| TINYINT |
-128 a 127 |
Idade, códigos de status |
| SMALLINT |
-32K a 32K |
Quantidades |
| INT |
-2.1B a 2.1B |
IDs, contagens |
| BIGINT |
Muito grande |
IDs grandes, timestamps |
| DECIMAL(p,s) |
Precisão exata |
Dinheiro |
| FLOAT/DOUBLE |
Aproximado |
Dados científicos |
-- SEMPRE usar DECIMAL para dinheiro
price DECIMAL(10, 2) -- R$99.999.999,99
-- NUNCA usar FLOAT para dinheiro
price FLOAT -- Erros de arredondamento!
Tipos Data/Hora
DATE -- 2025-10-31
TIME -- 14:30:00
DATETIME -- 2025-10-31 14:30:00
TIMESTAMP -- Conversão automática de timezone
-- Sempre armazenar em UTC
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
Booleano
-- PostgreSQL
is_active BOOLEAN DEFAULT TRUE
-- MySQL
is_active TINYINT(1) DEFAULT 1
Quando Criar Índices
| Sempre Indexar |
Razão |
| Chaves estrangeiras |
Acelera JOINs |
| Colunas em cláusula WHERE |
Acelera filtragem |
| Colunas em ORDER BY |
Acelera ordenação |
| Restrições únicas |
Força unicidade |
-- Índice de chave estrangeira
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- Índice de padrão de consulta
CREATE INDEX idx_orders_status_date ON orders(status, created_at);
Tipos de Índice
| Tipo |
Melhor Para |
Exemplo |
| B-Tree |
Intervalos, igualdade |
price > 100 |
| Hash |
Apenas correspondência exata |
email = 'x@y.com' |
| Full-text |
Busca de texto |
MATCH AGAINST |
| Parcial |
Subconjunto de linhas |
WHERE is_active = true |
Ordem de Índice Composto
CREATE INDEX idx_customer_status ON orders(customer_id, status);
-- Usa índice (customer_id primeiro)
SELECT * FROM orders WHERE customer_id = 123;
SELECT * FROM orders WHERE customer_id = 123 AND status = 'pending';
-- NÃO usa índice (status sozinho)
SELECT * FROM orders WHERE status = 'pending';
Regra: Coluna mais seletiva primeiro, ou coluna consultada sozinha com mais frequência.
Armadilhas de Índice
| Armadilha |
Problema |
Solução |
| Sobre-indexação |
Escritas lentas |
Indexar apenas o que é consultado |
| Ordem de coluna errada |
Índice não utilizado |
Corresponder padrões de consulta |
| Índices FK faltantes |
JOINs lentos |
Sempre indexar FKs |
Chaves Primárias
-- Auto-increment (simples)
id INT AUTO_INCREMENT PRIMARY KEY
-- UUID (sistemas distribuídos)
id CHAR(36) PRIMARY KEY DEFAULT (UUID())
-- Composta (tabelas de junção)
PRIMARY KEY (student_id, course_id)
Chaves Estrangeiras
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE -- Deletar filhos com pai
ON DELETE RESTRICT -- Prevenir deleção se referenciado
ON DELETE SET NULL -- Definir como NULL quando pai deletado
ON UPDATE CASCADE -- Atualizar filhos quando pai muda
| Estratégia |
Usar Quando |
| CASCADE |
Dados dependentes (order_items) |
| RESTRICT |
Referências importantes (prevenir acidentes) |
| SET NULL |
Relacionamentos opcionais |
Outras Restrições
-- Único
email VARCHAR(255) UNIQUE NOT NULL
-- Único composto
UNIQUE (student_id, course_id)
-- Check
price DECIMAL(10,2) CHECK (price >= 0)
discount INT CHECK (discount BETWEEN 0 AND 100)
-- Not null
name VARCHAR(100) NOT NULL
Um para Muitos
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT NOT NULL REFERENCES customers(id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id INT NOT NULL,
quantity INT NOT NULL
);
Muitos para Muitos
-- Tabela de junção
CREATE TABLE enrollments (
student_id INT REFERENCES students(id) ON DELETE CASCADE,
course_id INT REFERENCES courses(id) ON DELETE CASCADE,
enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (student_id, course_id)
);
Auto-Referenciado
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
manager_id INT REFERENCES employees(id)
);
Polimórfico
-- Abordagem 1: FKs separadas (integridade mais forte)
CREATE TABLE comments (
id INT PRIMARY KEY,
content TEXT NOT NULL,
post_id INT REFERENCES posts(id),
photo_id INT REFERENCES photos(id),
CHECK (
(post_id IS NOT NULL AND photo_id IS NULL) OR
(post_id IS NULL AND photo_id IS NOT NULL)
)
);
-- Abordagem 2: Tipo + ID (flexível, integridade mais fraca)
CREATE TABLE comments (
id INT PRIMARY KEY,
content TEXT NOT NULL,
commentable_type VARCHAR(50) NOT NULL,
commentable_id INT NOT NULL
);
Embedding vs Referencing
| Fator |
Embed |
Reference |
| Padrão de acesso |
Ler junto |
Ler separadamente |
| Relacionamento |
1:poucos |
1:muitos |
| Tamanho do documento |
Pequeno |
Próximo de 16MB |
| Frequência de atualização |
Raramente |
Frequentemente |
Documento Embedido
{
"_id": "order_123",
"customer": {
"id": "cust_456",
"name": "Jane Smith",
"email": "jane@example.com"
},
"items": [
{ "product_id": "prod_789", "quantity": 2, "price": 29.99 }
],
"total": 109.97
}
Documento Referenciado
{
"_id": "order_123",
"customer_id": "cust_456",
"item_ids": ["item_1", "item_2"],
"total": 109.97
}
Índices MongoDB
// Campo único
db.users.createIndex({ email: 1 }, { unique: true });
// Composto
db.orders.createIndex({ customer_id: 1, created_at: -1 });
// Busca de texto
db.articles.createIndex({ title: "text", content: "text" });
// Geoespacial
db.stores.createIndex({ location: "2dsphere" });
Boas Práticas de Migração
| Prática |
POR QUÊ |
| Sempre reversível |
Precisa reverter |
| Compatível com versão anterior |
Zero-downtime deploys |
| Schema antes dos dados |
Separar responsabilidades |
| Testar em staging |
Pegar problemas cedo |
Adicionar Coluna (Zero-Downtime)
-- Passo 1: Adicionar coluna nullable
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Passo 2: Deploy de código que escreve na nova coluna
-- Passo 3: Backfill de linhas existentes
UPDATE users SET phone = '' WHERE phone IS NULL;
-- Passo 4: Fazer obrigatório (se necessário)
ALTER TABLE users MODIFY phone VARCHAR(20) NOT NULL;
Renomear Coluna (Zero-Downtime)
-- Passo 1: Adicionar nova coluna
ALTER TABLE users ADD COLUMN email_address VARCHAR(255);
-- Passo 2: Copiar dados
UPDATE users SET email_address = email;
-- Passo 3: Deploy de código lendo da nova coluna
-- Passo 4: Deploy de código escrevendo na nova coluna
-- Passo 5: Dropar coluna antiga
ALTER TABLE users DROP COLUMN email;
Template de Migração
-- Migration: YYYYMMDDHHMMSS_description.sql
-- UP
BEGIN;
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_users_phone ON users(phone);
COMMIT;
-- DOWN
BEGIN;
DROP INDEX idx_users_phone ON users;
ALTER TABLE users DROP COLUMN phone;
COMMIT;
Análise de Consulta
EXPLAIN SELECT * FROM orders
WHERE customer_id = 123 AND status = 'pending';
| Procure Por |
Significado |
| type: ALL |
Full table scan (ruim) |
| type: ref |
Índice usado (bom) |
| key: NULL |
Nenhum índice usado |
| rows: alto |
Muitas linhas escaneadas |
Problema N+1 Queries
# RUIM: N+1 queries
orders = db.query("SELECT * FROM orders")
for order in orders:
customer = db.query(f"SELECT * FROM customers WHERE id = {order.customer_id}")
# BOM: Single JOIN
results = db.query("""
SELECT orders.*, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.id
""")
Técnicas de Otimização
| Técnica |
Quando Usar |
| Adicionar índices |
WHERE/ORDER BY lentos |
| Desnormalizar |
JOINs caros |
| Paginação |
Grandes conjuntos de resultados |
| Caching |
Consultas repetidas |
| Read replicas |
Carga de leitura pesada |
| Particionamento |
Tabelas muito grandes |
Pontos de Extensão
- Padrões Específicos de Banco de Dados: Adicionar variações MySQL vs PostgreSQL vs SQLite
- Padrões Avançados: Time-series, event sourcing, CQRS, multi-tenancy
- Integração com ORM: Padrões TypeORM, Prisma, SQLAlchemy
- Monitoramento: Rastreamento de desempenho de consultas, alertas de consulta lenta