Jhonatan Pinheiro
Loading page...
Jhonatan Pinheiro
Loading page...
Jhonatan Pinheiro
Loading page...
SQL or NoSQL? The five NoSQL families, ACID vs BASE, the CAP theorem, the same system modeled both ways, and when each one is the right call.
"Devo usar SQL ou NoSQL?" é a pergunta errada. A certa é: que forma os meus dados têm, e que garantias eu preciso ter sobre eles? Responda isso e a escolha do banco aparece sozinha.
Este guia mostra os dois mundos lado a lado — mesma aplicação, modelagens diferentes — com os comandos de cada um.
Um banco relacional guarda dados em tabelas — linhas e colunas com tipos definidos —, e os relacionamentos entre elas são declarados e garantidos pelo próprio banco.
Três ideias sustentam tudo:
CREATE TABLE clientes (
id BIGSERIAL PRIMARY KEY,
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) UNIQUE NOT NULL
);
CREATE TABLE pedidos (
id BIGSERIAL PRIMARY KEY,
cliente_id BIGINT NOT NULL REFERENCES clientes (id),
total DECIMAL(12,2) NOT NULL CHECK (total >= 0),
criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);Repare no que esse trecho já garante, sem uma linha de código de aplicação: e-mail não repete, total nunca é negativo, e nenhum pedido aponta para um cliente inexistente. Essa é a proposta de valor do modelo relacional: a regra mora no dado, não em quem escreve nele.
Principais nomes: PostgreSQL, MySQL, SQL Server, Oracle, Firebird, SQLite, MariaDB, DB2.
"NoSQL" nunca quis dizer "sem SQL" — hoje se lê como Not Only SQL. É um guarda-chuva para bancos que abriram mão de parte do modelo relacional para ganhar outra coisa: escala horizontal, flexibilidade de esquema ou desempenho em um padrão de acesso específico.
O que costuma mudar:
cliente_id existe — quem garante é o seu código.// MongoDB: o pedido carrega o que precisa junto
db.pedidos.insertOne({
_id: ObjectId(),
cliente: { id: 42, nome: "Ana Souza", email: "ana@email.com" },
itens: [
{ produto: "Teclado", qtd: 1, preco: 199.9 },
{ produto: "Mouse", qtd: 2, preco: 89.9 }
],
total: 379.7,
criadoEm: new Date()
});Uma leitura traz o pedido inteiro, pronto para a tela. O preço disso: se a Ana mudar de e-mail, esse documento continua com o antigo — e você precisa decidir se isso é um bug ou exatamente o que queria (um retrato histórico do pedido).
Tratar "NoSQL" como uma categoria só é o erro mais comum. São famílias com propósitos bem diferentes.
| Tipo | Como guarda | Bom para | Exemplos |
|---|---|---|---|
| Documento | JSON/BSON aninhado | Catálogos, perfis, conteúdo com formato variável | MongoDB, CouchDB, Firestore |
| Chave-valor | Chave → valor opaco | Cache, sessão, contador, fila | Redis, DynamoDB, Memcached |
| Colunar | Famílias de colunas, distribuído | Escrita massiva, séries, telemetria | Cassandra, HBase, ScyllaDB |
| Grafo | Nós e arestas | Relacionamentos profundos, recomendação, fraude | Neo4j, ArangoDB, Neptune |
| Vetorial / série temporal | Embeddings ou pontos no tempo | Busca semântica, métricas, IoT | Pinecone, Qdrant, InfluxDB, TimescaleDB |
db.produtos.find({ categoria: "games", preco: { $lt: 500 } })
.sort({ preco: -1 })
.limit(10);SET sessao:abc123 '{"userId":42}' EX 3600 # expira em 1 hora
GET sessao:abc123
INCR contador:visitas:2026-08-10-- Cassandra (CQL): a modelagem nasce da consulta, não do domínio
CREATE TABLE eventos_por_usuario (
usuario_id UUID,
quando TIMESTAMP,
tipo TEXT,
PRIMARY KEY (usuario_id, quando)
) WITH CLUSTERING ORDER BY (quando DESC);// Neo4j (Cypher): "amigos de amigos que curtem o que eu curto"
MATCH (eu:Pessoa {id: 42})-[:AMIGO*2]-(sugestao:Pessoa)
WHERE NOT (eu)-[:AMIGO]-(sugestao)
RETURN sugestao.nome, COUNT(*) AS forca
ORDER BY forca DESC LIMIT 10;Esse é o caso em que o NoSQL ganha de forma incontestável: a mesma pergunta em SQL exigiria JOINs recursivos e ficaria muito mais lenta conforme a profundidade aumenta.
-- pgvector: busca semântica dentro do próprio PostgreSQL
SELECT titulo, embedding <-> $1 AS distancia
FROM documentos ORDER BY distancia LIMIT 5;Um blog com posts, autores e comentários.
Relacional — três tabelas, cada fato uma vez:
CREATE TABLE autores (
id BIGSERIAL PRIMARY KEY,
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) UNIQUE NOT NULL
);
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
autor_id BIGINT NOT NULL REFERENCES autores (id),
titulo VARCHAR(200) NOT NULL,
corpo TEXT NOT NULL,
publicado_em TIMESTAMP
);
CREATE TABLE comentarios (
id BIGSERIAL PRIMARY KEY,
post_id BIGINT NOT NULL REFERENCES posts (id) ON DELETE CASCADE,
autor_nome VARCHAR(120) NOT NULL,
texto TEXT NOT NULL,
criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- A tela do post: um JOIN
SELECT p.titulo, p.corpo, a.nome AS autor
FROM posts p JOIN autores a ON a.id = p.autor_id
WHERE p.id = 1;Documento — o post carrega o que a tela precisa:
db.posts.insertOne({
_id: 1,
titulo: "Bancos relacionais e não relacionais",
corpo: "...",
autor: { id: 7, nome: "Jhonatan Pinheiro" }, // desnormalizado
comentarios: [ // aninhado
{ autorNome: "Ana", texto: "Ótimo post!", criadoEm: new Date() }
],
publicadoEm: new Date()
});
// A tela do post: uma leitura, sem JOIN
db.posts.findOne({ _id: 1 });O trade-off aparece na hora de mudar: renomear o autor é um UPDATE em uma linha no relacional, e um updateMany em todos os documentos dele no NoSQL. Já a leitura da página é uma busca só no documento — e um JOIN no relacional.
Aninhar comentários dentro do post funciona bem até o post viral com 50 mil comentários. Documento tem limite de tamanho (16 MB no MongoDB) e cresce a cada escrita. Coleção separada volta a ser a resposta.
ACID é o contrato dos bancos relacionais:
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 1;
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;
COMMIT; -- ou nada aconteceBASE é a postura de boa parte do NoSQL distribuído: Basically Available, Soft state, Eventually consistent. O sistema aceita ficar temporariamente incoerente entre réplicas em troca de disponibilidade e escala.
O teorema CAP explica por quê: um sistema distribuído sujeito a partição de rede precisa escolher entre consistência e disponibilidade. Não é uma escolha filosófica — é o que acontece quando o cabo entre dois data centers cai.
| Perfil | Escolha | Comportamento na partição | Exemplos |
|---|---|---|---|
| CP | Consistência | Recusa operações para não divergir | MongoDB (padrão), HBase, etcd |
| AP | Disponibilidade | Aceita e reconcilia depois | Cassandra, DynamoDB, Riak |
| CA | Só sem partição | Vale para um nó único | PostgreSQL em servidor único |
Vale corrigir dois mitos comuns: NoSQL não é sinônimo de "sem transação" (MongoDB tem transação multi-documento desde a 4.0) e relacional não é sinônimo de "não escala" (existe PostgreSQL com dezenas de terabytes em produção).
A mesma pergunta — "os 10 produtos de games mais caros abaixo de 500" — em cada linguagem:
-- SQL
SELECT nome, preco FROM produtos
WHERE categoria = 'games' AND preco < 500
ORDER BY preco DESC LIMIT 10;// MongoDB
db.produtos.find(
{ categoria: "games", preco: { $lt: 500 } },
{ nome: 1, preco: 1 }
).sort({ preco: -1 }).limit(10);-- Cassandra (CQL): só filtra pelo que está na chave; o resto exige outra tabela
SELECT nome, preco FROM produtos_por_categoria
WHERE categoria = 'games' LIMIT 10;// Neo4j
MATCH (p:Produto {categoria: 'games'})
WHERE p.preco < 500
RETURN p.nome, p.preco ORDER BY p.preco DESC LIMIT 10;E a agregação — faturamento por mês:
-- SQL
SELECT DATE_TRUNC('month', criado_em) AS mes, SUM(total) AS faturamento
FROM pedidos GROUP BY 1 ORDER BY 1;// MongoDB — aggregation pipeline
db.pedidos.aggregate([
{ $group: {
_id: { $dateTrunc: { date: "$criadoEm", unit: "month" } },
faturamento: { $sum: "$total" }
} },
{ $sort: { _id: 1 } }
]);A diferença de fundo: SQL é declarativo e padronizado — você aprende uma vez e leva para qualquer banco relacional. Cada NoSQL tem a sua linguagem, e trocar de banco costuma significar reescrever a camada de dados.
Bancos relacionais escalam leitura com naturalidade por réplicas:
-- PostgreSQL: réplica de leitura recebe os relatórios
SELECT pg_is_in_recovery(); -- true = é réplicaEscrita é o ponto difícil: só um primário aceita escrita. As saídas são particionamento, sharding na aplicação ou extensões como Citus.
-- Particionamento nativo: uma partição por mês
CREATE TABLE eventos (id BIGSERIAL, criado_em DATE NOT NULL, dados JSONB)
PARTITION BY RANGE (criado_em);
CREATE TABLE eventos_2026_08 PARTITION OF eventos
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');Nos bancos distribuídos, o sharding é premissa de projeto:
// MongoDB: shard key define a distribuição — e é quase impossível de mudar depois
sh.shardCollection("loja.pedidos", { clienteId: "hashed" });A escolha da chave de shard é a decisão mais cara de um banco distribuído. Chave ruim gera hot partition: um nó com 90% do tráfego e o resto do cluster ocioso.
Use relacional quando (e este é o padrão para a maioria dos sistemas):
Use documento quando:
Use chave-valor quando:
Use colunar quando:
Use grafo quando:
Use vetorial quando:
Um resumo prático:
| Necessidade | Escolha natural |
|---|---|
| Transação financeira | Relacional |
| Carrinho e sessão | Chave-valor |
| Catálogo com atributos variáveis | Documento |
| Métricas de sensores | Colunar / série temporal |
| "Quem conhece quem" | Grafo |
| Relatório com filtro imprevisível | Relacional |
| Busca semântica sobre texto | Vetorial |
Do lado NoSQL:
Do lado relacional:
Sistemas maduros raramente usam um banco só. Uma arquitetura comum de e-commerce:
| Componente | Banco | Por quê |
|---|---|---|
| Pedidos, pagamentos, estoque | PostgreSQL | Transação e integridade são inegociáveis |
| Sessão, carrinho, rate limit | Redis | Latência baixíssima e expiração nativa |
| Catálogo e busca | Elasticsearch / OpenSearch | Busca textual com ranking e faceta |
| Eventos e clickstream | Cassandra / Kafka + colunar | Escrita massiva contínua |
| Recomendação | Neo4j ou pgvector | Relacionamento e similaridade |
O custo desse desenho é real: mais peças para operar, monitorar, versionar e manter em sincronia. Comece com um banco relacional bem modelado e adicione uma peça especializada quando um gargalo concreto aparecer — nunca por antecipação.
Relacional e não relacional não competem: resolvem problemas diferentes.
A pergunta final nunca é "qual é o melhor banco". É "quais garantias eu preciso, e qual é o preço que estou disposto a pagar por elas".