Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
¿SQL o NoSQL? Los cinco tipos de NoSQL, ACID frente a BASE, el teorema CAP, el mismo sistema modelado de las dos formas y cuándo usar cada uno.
"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".