Volver al inicio
Backend

Backend - Bases de Datos

Conceptos fundamentales sobre bases de datos: SQL, índices, JOINs, transacciones ACID, normalización, NoSQL, MongoDB y consistencia eventual

ConceptosScreening

Introducción

Las bases de datos son fundamentales en el desarrollo backend. Entender SQL, NoSQL y sus conceptos clave te ayudará a diseñar sistemas eficientes y escalables.

SQL - Índices

1 preguntas

1

Índices: qué son y cuándo usarlos

Definición: Estructura de datos que mejora la velocidad de búsqueda en tablas.
Cómo funcionan:
•Similar a índice de un libro
•Crea estructura ordenada de valores
•Permite búsqueda rápida sin escanear toda la tabla
•Trade-off: Más espacio, más lento en escrituras
Cuándo crear índices:
✓Columnas en WHERE frecuentemente:
sql
WHERE user_id = ? -- Crear índice en user_id
WHERE status = 'active' -- Si hay muchos valores únicos
✓Columnas en JOIN:
sql
JOIN orders ON orders.user_id = users.id
-- Índice en orders.user_id
✓Columnas en ORDER BY:
sql
ORDER BY created_at DESC
-- Índice en created_at
✓Columnas en GROUP BY:
sql
GROUP BY category_id
-- Índice en category_id
Cuándo NO crear índices:
✗ Tablas pequeñas (< 1000 filas)
✗ Columnas que cambian frecuentemente
✗ Columnas con pocos valores únicos (ej: género)
✗ Columnas raramente usadas en consultas
Tipos de índices:
TipoDescripciónCuándo usar
B-Tree
Estándar, balanceado
Mayoría de casos (default)
Hash
Lookup O(1)
Igualdad exacta, no rangos
Bitmap
Para pocos valores únicos
Columnas booleanas, estados
Full-Text
Búsqueda de texto completo
Búsquedas en texto largo
Composite
Múltiples columnas
Consultas con múltiples WHERE
Ejemplo:
sql
-- Crear índice simple
CREATE INDEX idx_users_email ON users(email);

-- Consulta rápida (usa índice)
SELECT * FROM users WHERE email = 'user@example.com';

-- Índice compuesto
CREATE INDEX idx_orders_user_status 
ON orders(user_id, status, created_at);

-- Útil para: WHERE user_id = ? AND status = ? ORDER BY created_at
Impacto en rendimiento:
•Sin índice: O(n) - escanea toda la tabla
•Con índice: O(log n) - búsqueda binaria
•Diferencia: De segundos a milisegundos en tablas grandes
Trade-offs:
•Espacio: Índices ocupan espacio adicional
•Escritura: INSERT/UPDATE/DELETE más lentos
•Mantenimiento: Índices necesitan mantenimiento
•Balance: No crear demasiados índices

SQL - JOIN vs SUBQUERY

1 preguntas

1

JOIN vs SUBQUERY: ¿Cuándo usar cada uno?

Comparación:
AspectoJOINSUBQUERY
Rendimiento
✅ Generalmente más rápido
⚠️ Puede ser más lento
Legibilidad
✅ Más clara
⚠️ Puede ser confusa
Flexibilidad
⚠️ Limitada
✅ Más flexible
Uso de índices
✅ Mejor optimización
⚠️ Depende del optimizador
Complejidad
✅ Simple
❌ Puede ser compleja
JOIN - Usa cuando:
✓Necesitas datos de múltiples tablas:
sql
-- ✅ JOIN
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';
✓Optimización importante:
• Mejor uso de índices
• Optimizador puede optimizar mejor
✓Consultas simples:
• Relaciones directas
• Filtros simples
SUBQUERY - Usa cuando:
✓Condiciones complejas:
sql
-- ✅ SUBQUERY
SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
  WHERE total > 1000
);
✓Agregaciones en condiciones:
sql
-- ✅ SUBQUERY con agregación
SELECT * FROM products
WHERE price > (
  SELECT AVG(price) FROM products
);
✓EXISTS para existencia:
sql
-- ✅ EXISTS (más eficiente que IN)
SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.user_id = u.id
);
Ejemplos comparativos:
JOIN:
sql
-- Obtener usuarios con sus órdenes
SELECT u.name, o.total, o.created_at
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01';
SUBQUERY equivalente:
sql
-- Menos eficiente
SELECT u.name,
  (SELECT total FROM orders WHERE user_id = u.id LIMIT 1) as total
FROM users u
WHERE u.created_at > '2024-01-01';
Mejores prácticas:
✓Preferir JOIN para:
• Datos de múltiples tablas
• Cuando necesitas todas las columnas
• Rendimiento crítico
✓Usar SUBQUERY para:
• Condiciones complejas
• Agregaciones en WHERE
• EXISTS para verificar existencia
✓EXISTS vs IN:
• EXISTS generalmente más eficiente
• IN puede ser más legible para listas pequeñas
Regla general:
•JOIN para combinar datos de tablas
•SUBQUERY para condiciones complejas o agregaciones

SQL - Transacciones y ACID

1 preguntas

1

Transacciones y ACID: ¿Qué son y por qué importan?

Definición: Transacción es una secuencia de operaciones que se ejecutan como una unidad atómica.
ACID - Propiedades:
A - Atomicity (Atomicidad):
•Todas las operaciones se ejecutan o ninguna
•"Todo o nada"
•Si falla una, se revierte todo
C - Consistency (Consistencia):
•La base de datos siempre queda en estado válido
•Se cumplen todas las reglas de integridad
•No se violan constraints
I - Isolation (Aislamiento):
•Transacciones concurrentes no se interfieren
•Cada transacción ve datos consistentes
•Niveles de aislamiento controlan visibilidad
D - Durability (Durabilidad):
•Cambios persisten después de commit
•Sobrevive a fallos del sistema
•Escritos a disco permanente
Ejemplo de transacción:
sql
BEGIN TRANSACTION;

-- Transferencia de dinero
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- Si todo OK
COMMIT;

-- Si hay error
ROLLBACK;
Niveles de aislamiento (de menor a mayor):
NivelDescripciónProblemas que evita
READ UNCOMMITTED
Lee datos no confirmados
Ninguno
READ COMMITTED
Solo lee datos confirmados
Dirty reads
REPEATABLE READ
Lecturas consistentes
Dirty reads, Non-repeatable reads
SERIALIZABLE
Aislamiento total
Todos (dirty reads, non-repeatable, phantom reads)
Problemas de concurrencia:
✗ Dirty Read: Leer datos no confirmados
✗ Non-repeatable Read: Mismo dato cambia entre lecturas
✗ Phantom Read: Aparecen nuevas filas entre lecturas
Ejemplo práctico:
sql
-- Transferencia bancaria (debe ser atómica)
BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Si falla aquí, todo se revierte
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT; -- Confirma cambios
-- O ROLLBACK; -- Revierte todo
Cuándo usar transacciones:
✓Operaciones que deben ser atómicas
✓Múltiples operaciones relacionadas
✓Mantener consistencia de datos
✓Operaciones críticas (dinero, inventario)
Trade-offs:
•Mayor aislamiento = mejor consistencia pero menor concurrencia
•Menor aislamiento = mejor rendimiento pero posibles inconsistencias

SQL - Normalización vs Desnormalización

1 preguntas

1

Normalización vs Desnormalización: ¿Cuándo usar cada una?

Normalización: Proceso de organizar datos para reducir redundancia y mejorar integridad.
Formas normales:
1NF (Primera Forma Normal):
•Cada columna contiene valores atómicos
•No hay arrays o listas en una celda
2NF (Segunda Forma Normal):
•Está en 1NF
•Todos los atributos no clave dependen completamente de la clave primaria
3NF (Tercera Forma Normal):
•Está en 2NF
•No hay dependencias transitivas
•Atributos no clave no dependen de otros atributos no clave
Ejemplo normalizado:
sql
-- Tablas separadas
users (id, name, email)
orders (id, user_id, total, created_at)
order_items (id, order_id, product_id, quantity)
products (id, name, price)

-- Relaciones claras, sin redundancia
Ventajas de normalización:
✓Menos redundancia: Datos no duplicados
✓Integridad: Más fácil mantener consistencia
✓Menos espacio: Almacenamiento eficiente
✓Actualizaciones: Cambiar en un solo lugar
Desventajas de normalización:
✗ Más JOINs: Consultas más complejas
✗ Rendimiento: Múltiples tablas pueden ser más lentas
✗ Complejidad: Más tablas para manejar
Desnormalización: Proceso intencional de agregar redundancia para mejorar rendimiento.
Ejemplo desnormalizado:
sql
-- Agregar datos redundantes para evitar JOINs
orders (
  id,
  user_id,
  user_name,  -- Redundante, pero evita JOIN
  total,
  created_at
)
Cuándo desnormalizar:
✓Lecturas frecuentes:
• Muchas más lecturas que escrituras
• JOINs costosos en consultas frecuentes
✓Rendimiento crítico:
• Consultas deben ser muy rápidas
• Latencia es problema
✓Datos de solo lectura:
• Datos históricos que no cambian
• Reportes y analytics
✓Caching:
• Datos calculados pre-computados
• Agregaciones frecuentes
Estrategia híbrida:
✓Normalizar para escritura:
• Mantener datos normalizados como fuente de verdad
✓Desnormalizar para lectura:
• Crear vistas materializadas
• Tablas de denormalización para consultas
• Sincronizar periódicamente
Ejemplo híbrido:
sql
-- Tabla normalizada (fuente de verdad)
orders_normalized (id, user_id, total)
users_normalized (id, name)

-- Vista materializada desnormalizada (para lectura)
CREATE MATERIALIZED VIEW orders_denormalized AS
SELECT o.id, o.total, u.name as user_name
FROM orders_normalized o
JOIN users_normalized u ON o.user_id = u.id;

-- Refresh periódico
REFRESH MATERIALIZED VIEW orders_denormalized;
Recomendación:
•Empezar normalizado (3NF)
•Desnormalizar solo cuando sea necesario para rendimiento
•Medir antes y después
•Documentar decisiones de desnormalización

NoSQL

3 preguntas

1

MongoDB vs SQL (cuándo usar cada uno)

Comparación rápida:
AspectoSQL (Relacional)MongoDB (NoSQL)
Modelo
Tablas con relaciones
Documentos (JSON-like)
Esquema
✅ Fijo, rígido
✅ Flexible, dinámico
ACID
✅ Completo
⚠️ Parcial (transacciones multi-documento)
Escalabilidad
⚠️ Vertical principalmente
✅ Horizontal (sharding)
JOINs
✅ Nativo
❌ No (usar embedding/referencing)
Consultas complejas
✅ SQL poderoso
⚠️ Limitado
Consistencia
✅ Fuerte
⚠️ Eventual (por defecto)
Uso común
Datos estructurados, relaciones
Datos semi-estructurados, escalabilidad
SQL - Usa cuando:
✓Datos estructurados:
• Esquema bien definido
• Relaciones complejas entre entidades
✓Transacciones críticas:
• Necesitas ACID completo
• Consistencia fuerte requerida
• Operaciones financieras
✓Consultas complejas:
• Múltiples JOINs
• Agregaciones complejas
• Reportes analíticos
✓Integridad de datos:
• Constraints y validaciones
• Relaciones foreign key
Ejemplo SQL:
sql
-- Consulta compleja con JOINs
SELECT u.name, COUNT(o.id) as order_count, SUM(o.total) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5;
MongoDB - Usa cuando:
✓Datos semi-estructurados:
• Esquema variable
• Documentos con estructura diferente
✓Escalabilidad horizontal:
• Necesitas sharding
• Crecimiento masivo de datos
✓Desarrollo rápido:
• Prototipado
• Cambios frecuentes de esquema
✓Datos jerárquicos:
• Estructura de árbol
• Datos anidados naturalmente
Ejemplo MongoDB:
javascript
// Documento con datos anidados
{
  _id: ObjectId("..."),
  name: "Juan",
  orders: [
    { product: "Laptop", total: 1000 },
    { product: "Mouse", total: 20 }
  ]
}

// Consulta
db.users.find({
  "orders.total": { $gt: 500 }
});
Cuándo usar cada uno:
EscenarioRecomendaciónRazón
E-commerce
SQL
Relaciones complejas, transacciones
Content Management
MongoDB
Contenido variable, escalabilidad
Analytics/Logs
MongoDB
Datos masivos, estructura flexible
Sistema bancario
SQL
ACID crítico, consistencia fuerte
Social Media
MongoDB
Escalabilidad, datos semi-estructurados
ERP/CRM
SQL
Relaciones complejas, reportes
Híbrido:
✓Usar SQL para datos transaccionales
✓Usar MongoDB para logs, analytics, cache
✓Cada base de datos para su propósito
2

Embedding vs Referencing en MongoDB

Embedding: Almacenar datos relacionados dentro del mismo documento.
Referencing: Almacenar referencia (ID) a otro documento.
Comparación:
AspectoEmbeddingReferencing
Consultas
✅ Una sola query
❌ Múltiples queries
Actualización
❌ Actualizar múltiples documentos
✅ Actualizar uno
Tamaño documento
❌ Puede crecer mucho
✅ Documentos pequeños
Relaciones
⚠️ 1:1 o 1:muchos pequeños
✅ Cualquier relación
Consistencia
✅ Atómica
⚠️ Múltiples documentos
Embedding - Usa cuando:
✓Datos que se leen juntos:
javascript
// ✅ Embedding
{
  _id: ObjectId("..."),
  name: "Juan",
  address: {
    street: "123 Main St",
    city: "Madrid",
    zip: "28001"
  }
}
✓Relación 1:1 o 1:muchos pequeña:
• Usuario y perfil
• Orden y items (pocos items)
✓Datos que no cambian frecuentemente:
• Dirección de usuario
• Preferencias
✓Necesitas atomicidad:
• Operaciones atómicas
• Transacciones simples
Referencing - Usa cuando:
✓Relación 1:muchos grande:
javascript
// ✅ Referencing
// Usuario
{
  _id: ObjectId("user123"),
  name: "Juan",
  orderIds: [ObjectId("order1"), ObjectId("order2")]
}

// Órdenes separadas
{
  _id: ObjectId("order1"),
  userId: ObjectId("user123"),
  total: 100
}
✓Datos que cambian frecuentemente:
• Comentarios en posts
• Likes
✓Datos compartidos:
• Productos en múltiples órdenes
• Usuarios en múltiples grupos
✓Tamaño variable grande:
• Listas que pueden crecer mucho
• Arrays muy grandes
Ejemplo práctico:
Embedding (bueno para pocos items):
javascript
// Orden con pocos items
{
  _id: ObjectId("..."),
  userId: ObjectId("..."),
  items: [
    { product: "Laptop", quantity: 1, price: 1000 },
    { product: "Mouse", quantity: 2, price: 20 }
  ],
  total: 1040
}
Referencing (bueno para muchos items):
javascript
// Orden
{
  _id: ObjectId("order123"),
  userId: ObjectId("user456"),
  itemIds: [ObjectId("item1"), ObjectId("item2"), ...],
  total: 1040
}

// Items separados
{
  _id: ObjectId("item1"),
  orderId: ObjectId("order123"),
  product: "Laptop",
  quantity: 1,
  price: 1000
}
Regla general:
•Embedding: Datos que se leen juntos, relación pequeña, cambios infrecuentes
•Referencing: Relación grande, datos que cambian frecuentemente, datos compartidos
3

¿Qué es eventual consistency?

Definición: Modelo de consistencia donde el sistema eventualmente alcanzará consistencia, pero no garantiza consistencia inmediata.
Consistencia fuerte vs eventual:
AspectoConsistencia FuerteEventual Consistency
Garantía
✅ Inmediata
⚠️ Eventual
Rendimiento
❌ Más lento
✅ Más rápido
Disponibilidad
❌ Menor
✅ Mayor
Uso
SQL tradicional
NoSQL distribuido
Cómo funciona:
[Escritura] → [Réplica A] ──┐
              [Réplica B] ──┼─→ [Eventualmente consistente]
              [Réplica C] ──┘
Ejemplo:
javascript
// Usuario actualiza perfil
UPDATE users SET name = 'Juan' WHERE id = 123;

// Réplica A: name = 'Juan' ✅
// Réplica B: name = 'Juan' ✅ (sincronizado)
// Réplica C: name = 'Juan Viejo' ⚠️ (aún no sincronizado)

// Después de unos segundos
// Réplica C: name = 'Juan' ✅ (ahora consistente)
Cuándo es aceptable:
✓Datos no críticos:
• Perfiles de usuario
• Contadores de likes
• Timelines de redes sociales
✓Alta disponibilidad:
• Sistema debe estar siempre disponible
• Tolerancia a fallos
✓Escalabilidad:
• Sistemas distribuidos grandes
• Múltiples regiones geográficas
Cuándo NO es aceptable:
✗ Datos críticos:
• Transacciones financieras
• Inventario crítico
• Operaciones médicas
✗ Operaciones atómicas:
• Transferencias de dinero
• Reservas
Patrones para manejar eventual consistency:
✓Versionado:
javascript
// Usar versiones para detectar conflictos
{
  _id: "...",
  name: "Juan",
  version: 5
}

// Al actualizar, verificar versión
if (currentVersion !== expectedVersion) {
  // Conflict, resolver
}
✓Vector Clocks:
• Rastrear causalidad de eventos
• Detectar conflictos
✓CRDTs (Conflict-free Replicated Data Types):
• Estructuras de datos que se resuelven automáticamente
• Sin conflictos
✓Saga Pattern:
• Para transacciones distribuidas
• Compensación en caso de fallo
Ejemplo real:
Red social (eventual consistency OK):
javascript
// Usuario A sigue a Usuario B
// Puede tomar unos segundos en aparecer en todos los feeds
// Aceptable para este caso de uso
Sistema bancario (consistencia fuerte requerida):
javascript
// Transferencia debe ser inmediata y consistente
// Eventual consistency NO es aceptable
Trade-off CAP Theorem:
•Consistency: Todos ven mismos datos
•Availability: Sistema siempre responde
•Partition tolerance: Funciona con fallos de red
No puedes tener los 3 simultáneamente:
•SQL: CP (Consistency + Partition tolerance)
•NoSQL: AP (Availability + Partition tolerance)
•Eventual consistency es parte de AP
Recomendación:
•Usar consistencia fuerte cuando sea crítico
•Aceptar eventual consistency cuando sea apropiado
•Documentar decisiones de consistencia