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.
"¿Debo usar SQL o NoSQL?" es la pregunta equivocada. La correcta es: ¿qué forma tienen mis datos y qué garantías necesito tener sobre ellos? Responde a eso y la elección de la base de datos aparece sola.
Esta guía muestra los dos mundos lado a lado — misma aplicación, modelados distintos — con los comandos de cada uno.
Una base de datos relacional guarda los datos en tablas — filas y columnas con tipos definidos —, y las relaciones entre ellas están declaradas y garantizadas por la propia base de datos.
Tres ideas lo sostienen todo:
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
);Fíjate en lo que ese fragmento ya garantiza, sin una línea de código de aplicación: el correo no se repite, el total nunca es negativo y ningún pedido apunta a un cliente inexistente. Esa es la propuesta de valor del modelo relacional: la regla vive en el dato, no en quien escribe sobre él.
Nombres principales: PostgreSQL, MySQL, SQL Server, Oracle, Firebird, SQLite, MariaDB, DB2.
"NoSQL" nunca quiso decir "sin SQL" — hoy se lee como Not Only SQL. Es un paraguas para bases de datos que renunciaron a parte del modelo relacional para ganar otra cosa: escala horizontal, flexibilidad de esquema o rendimiento en un patrón de acceso concreto.
Lo que suele cambiar:
cliente_id exista — quien lo garantiza es tu código.// MongoDB: el pedido lleva consigo lo que necesita
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()
});Una lectura trae el pedido entero, listo para la pantalla. El precio de eso: si Ana cambia de correo, ese documento sigue con el antiguo — y tienes que decidir si eso es un bug o exactamente lo que querías (una foto histórica del pedido).
Tratar "NoSQL" como una única categoría es el error más común. Son familias con propósitos muy distintos.
| Tipo | Cómo guarda | Bueno para | Ejemplos |
|---|---|---|---|
| Documento | JSON/BSON anidado | Catálogos, perfiles, contenido con formato variable | MongoDB, CouchDB, Firestore |
| Clave-valor | Clave → valor opaco | Caché, sesión, contador, cola | Redis, DynamoDB, Memcached |
| Columnar | Familias de columnas, distribuido | Escritura masiva, series, telemetría | Cassandra, HBase, ScyllaDB |
| Grafo | Nodos y aristas | Relaciones profundas, recomendación, fraude | Neo4j, ArangoDB, Neptune |
| Vectorial / serie temporal | Embeddings o puntos en el tiempo | Búsqueda 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 en 1 hora
GET sessao:abc123
INCR contador:visitas:2026-08-10-- Cassandra (CQL): el modelado nace de la consulta, no del dominio
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 a los que les gusta lo que a mí me gusta"
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;Este es el caso en que NoSQL gana de forma indiscutible: la misma pregunta en SQL exigiría JOINs recursivos y sería mucho más lenta a medida que crece la profundidad.
-- pgvector: búsqueda semántica dentro del propio PostgreSQL
SELECT titulo, embedding <-> $1 AS distancia
FROM documentos ORDER BY distancia LIMIT 5;Un blog con posts, autores y comentarios.
Relacional — tres tablas, cada hecho una 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
);
-- La pantalla del post: un 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 — el post lleva lo que la pantalla necesita:
db.posts.insertOne({
_id: 1,
titulo: "Bancos relacionais e não relacionais",
corpo: "...",
autor: { id: 7, nome: "Jhonatan Pinheiro" }, // desnormalizado
comentarios: [ // anidado
{ autorNome: "Ana", texto: "Ótimo post!", criadoEm: new Date() }
],
publicadoEm: new Date()
});
// La pantalla del post: una lectura, sin JOIN
db.posts.findOne({ _id: 1 });El trade-off aparece a la hora de cambiar algo: renombrar al autor es un UPDATE en una fila en el relacional, y un updateMany en todos sus documentos en NoSQL. En cambio, leer la página es una única búsqueda en el documento — y un JOIN en el relacional.
Anidar los comentarios dentro del post funciona bien hasta el post viral con 50 mil comentarios. Un documento tiene límite de tamaño (16 MB en MongoDB) y crece con cada escritura. Una colección aparte vuelve a ser la respuesta.
ACID es el contrato de las bases relacionales:
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 1;
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;
COMMIT; -- o no ocurre nadaBASE es la postura de buena parte del NoSQL distribuido: Basically Available, Soft state, Eventually consistent. El sistema acepta quedar temporalmente incoherente entre réplicas a cambio de disponibilidad y escala.
El teorema CAP explica por qué: un sistema distribuido sujeto a una partición de red debe elegir entre consistencia y disponibilidad. No es una elección filosófica — es lo que ocurre cuando cae el cable entre dos centros de datos.
| Perfil | Elección | Comportamiento en la partición | Ejemplos |
|---|---|---|---|
| CP | Consistencia | Rechaza operaciones para no divergir | MongoDB (por defecto), HBase, etcd |
| AP | Disponibilidad | Acepta y reconcilia después | Cassandra, DynamoDB, Riak |
| CA | Solo sin partición | Vale para un único nodo | PostgreSQL en servidor único |
Conviene corregir dos mitos comunes: NoSQL no es sinónimo de "sin transacciones" (MongoDB tiene transacciones multidocumento desde la 4.0) y relacional no es sinónimo de "no escala" (existe PostgreSQL con decenas de terabytes en producción).
La misma pregunta — "los 10 productos de games más caros por debajo de 500" — en cada lenguaje:
-- 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): solo filtra por lo que está en la clave; el resto exige otra tabla
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;Y la agregación — facturación por mes:
-- 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 } }
]);La diferencia de fondo: SQL es declarativo y está estandarizado — lo aprendes una vez y lo llevas a cualquier base relacional. Cada NoSQL tiene su propio lenguaje, y cambiar de base suele significar reescribir la capa de datos.
Las bases relacionales escalan la lectura con naturalidad mediante réplicas:
-- PostgreSQL: la réplica de lectura recibe los informes
SELECT pg_is_in_recovery(); -- true = es una réplicaLa escritura es el punto difícil: solo un primario acepta escrituras. Las salidas son el particionado, el sharding en la aplicación o extensiones como Citus.
-- Particionado nativo: una partición por mes
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');En las bases distribuidas, el sharding es una premisa de diseño:
// MongoDB: la shard key define la distribución — y es casi imposible de cambiar después
sh.shardCollection("loja.pedidos", { clienteId: "hashed" });La elección de la clave de shard es la decisión más cara de una base distribuida. Una clave mala genera una hot partition: un nodo con el 90% del tráfico y el resto del clúster ocioso.
Usa relacional cuando (y este es el valor por defecto para la mayoría de los sistemas):
Usa documento cuando:
Usa clave-valor cuando:
Usa columnar cuando:
Usa grafo cuando:
Usa vectorial cuando:
Un resumen práctico:
| Necesidad | Elección natural |
|---|---|
| Transacción financiera | Relacional |
| Carrito y sesión | Clave-valor |
| Catálogo con atributos variables | Documento |
| Métricas de sensores | Columnar / serie temporal |
| "Quién conoce a quién" | Grafo |
| Informe con filtro imprevisible | Relacional |
| Búsqueda semántica sobre texto | Vectorial |
Del lado NoSQL:
Del lado relacional:
Los sistemas maduros rara vez usan una sola base de datos. Una arquitectura habitual de e-commerce:
| Componente | Base de datos | Por qué |
|---|---|---|
| Pedidos, pagos, inventario | PostgreSQL | La transacción y la integridad son innegociables |
| Sesión, carrito, rate limit | Redis | Latencia bajísima y expiración nativa |
| Catálogo y búsqueda | Elasticsearch / OpenSearch | Búsqueda textual con ranking y facetas |
| Eventos y clickstream | Cassandra / Kafka + columnar | Escritura masiva continua |
| Recomendación | Neo4j o pgvector | Relaciones y similitud |
El coste de ese diseño es real: más piezas que operar, monitorizar, versionar y mantener sincronizadas. Empieza con una base relacional bien modelada y añade una pieza especializada cuando aparezca un cuello de botella concreto — nunca por anticipación.
Relacional y no relacional no compiten: resuelven problemas distintos.
La pregunta final nunca es "cuál es la mejor base de datos". Es "qué garantías necesito y qué precio estoy dispuesto a pagar por ellas".