🗄️ Bases de datos
Qué es una base de datos, SQL y el modelo relacional, relaciones y normalización, índices, transacciones y ACID, aislamiento, y los tipos de bases de datos (relacionales, NoSQL, in-memory). Con código y práctica.
Bases de datos
Una base de datos (DB) es un sistema para guardar datos de forma persistente y consultarlos de forma eficiente y segura. Es el corazón de casi todo backend: usuarios, pedidos, mensajes, configuraciones… Todo acaba en una base de datos.
Esta página te da el mapa completo: el modelo relacional, SQL, índices, transacciones y los tipos de bases de datos. Las wikis del sitio profundizan: SQL, Índices, Transacciones y Bases de datos en backend.
El modelo relacional y SQL
La forma dominante son las bases de datos relacionales (PostgreSQL, MySQL, SQLite, Oracle): los datos viven en tablas con filas y columnas, y las tablas se relacionan entre sí por claves. El lenguaje para hablar con ellas es SQL (Structured Query Language).
CREATE TABLE usuarios (
id SERIAL PRIMARY KEY,
nombre TEXT NOT NULL,
email TEXT UNIQUE,
creado_en TIMESTAMP DEFAULT now()
);
| Término | Significado |
|---|---|
| Tabla | Colección de filas con la misma estructura |
| Columna / campo | Una propiedad (nombre, email…) |
| Fila / registro | Una entrada concreta |
| Clave primaria (PK) | Identificador único de cada fila |
| Clave foránea (FK) | Referencia a la PK de otra tabla |
| Esquema | La estructura definida de las tablas |
CRUD
Las 4 operaciones básicas:
-- CREATE (insertar)
INSERT INTO usuarios (nombre, email) VALUES ('Ana', 'ana@mail.com');
-- READ (leer)
SELECT id, nombre FROM usuarios WHERE email = 'ana@mail.com';
-- UPDATE (actualizar)
UPDATE usuarios SET nombre = 'Ana María' WHERE id = 1;
-- DELETE (borrar)
DELETE FROM usuarios WHERE id = 1;
SELECT: la operación reina
SELECT id, nombre -- qué columnas
FROM usuarios -- de qué tabla
WHERE edad >= 18 -- filtro
AND pais = 'ES'
ORDER BY creado_en DESC -- ordenar
LIMIT 10 -- cuántas filas
OFFSET 20; -- saltar (paginación)
-- Uniones (JOIN): combinar tablas por relación
SELECT u.nombre, p.titulo
FROM usuarios u
JOIN pedidos p ON p.usuario_id = u.id
WHERE p.total > 100;
-- Agregaciones
SELECT pais, COUNT(*) AS cantidad, AVG(edad)
FROM usuarios
GROUP BY pais
HAVING COUNT(*) > 5;
💡 Orden de ejecución mental de SELECT:
FROM→JOIN→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT. ElWHEREfiltra antes de agrupar; elHAVINGfiltra después.
Relaciones
El modelo relacional modela el mundo con relaciones entre tablas:
| Relación | Cómo se modela | Ejemplo |
|---|---|---|
| 1 a 1 | FK única en un lado | usuario ↔ perfil |
| 1 a muchos | FK en la tabla «muchos» | usuario → pedidos |
| Muchos a muchos | Tabla puente (join table) | usuarios ↔ cursos |
-- Muchos a muchos: tabla puente
CREATE TABLE inscripciones (
usuario_id INT REFERENCES usuarios(id),
curso_id INT REFERENCES cursos(id),
PRIMARY KEY (usuario_id, curso_id)
);
Normalización
La normalización elimina datos duplicados y anomalías. Las reglas (formas normales) más importantes en la práctica:
- 1NF: cada celda tiene un solo valor (nada de listas en una columna).
- 2NF: ninguna columna depende de parte de la clave.
- 3NF: ninguna columna depende de otra columna no clave.
MAL (datos duplicados):
usuarios: id | nombre | direccion | pedido1 | pedido2
BIEN (normalizado):
usuarios: id | nombre | direccion
pedidos: id | usuario_id | total | fecha
⚠️ La normalización completa no siempre es lo mejor: en sistemas de lectura pesada se desnormaliza a propósito (redundancia controlada) para evitar JOINs costosos. Es un equilibrio que verás en system design.
Índices: por qué las búsquedas son rápidas
Sin índice, una búsqueda recorre toda la tabla (full scan, O(n)). Un índice es una estructura auxiliar (normalmente un árbol B+) que permite buscar en O(log n) — la búsqueda binaria de las bases de datos.
-- Crear índice en una columna que se filtra mucho
CREATE INDEX idx_usuarios_email ON usuarios (email);
-- Búsqueda que lo usa: antes O(n), ahora O(log n)
SELECT * FROM usuarios WHERE email = 'ana@mail.com';
Sin índice: usuarios —────────────────────▶ recorre 1.000.000 filas
Con índice: idx_email (B+tree) ──▶ 20 comparaciones ──▶ fila
💡 El motor elige el índice automáticamente (usa el query planner). Pistas para decidir: indexa columnas usadas en
WHERE,JOINyORDER BY. No indexes todo: cada índice ocupa espacio y encarece las inserciones. Los índices compuestos y las cubiertas son un tema entero — wiki: Índices.
⚠️ Un
SELECT * ... WHEREsobre una columna sin índice en una tabla de 1M de filas es un full table scan: una de las causas #1 de “la base de datos va lenta”. EjecutaEXPLAIN ANALYZEy verás si usa índice o scan.
Transacciones y ACID
Una transacción es un grupo de operaciones que se ejecutan como una sola unidad: o todas, o ninguna. El ejemplo clásico: transferir dinero (restar de A, sumar a B). Si el sistema se cae entre medias, no puede quedar a medias.
BEGIN; -- inicia la transacción
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;
UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2;
COMMIT; -- confirma (ambas operaciones)
-- ROLLBACK; -- en caso de error: deshace todo
ACID
| Letra | Significado | En una frase |
|---|---|---|
| A | Atomicidad | O todo o nada |
| C | Consistencia | Los datos quedan válidos según las reglas |
| I | Aislamiento | Las transacciones concurrentes no se pisan |
| D | Durabilidad | Lo confirmado sobrevive a un apagón |
Niveles de aislamiento
Cuanto más aisladas las transacciones, más seguras pero más lentas. Los niveles estándar (en orden de seguridad):
| Nivel | Fenómenos que evita | Nota |
|---|---|---|
| Read Uncommitted | Nada (puedes leer datos sin confirmar) | Casi nunca se usa |
| Read Committed | Lecturas sucias | El default de PostgreSQL y la mayoría |
| Repeatable Read | Lecturas no repetibles | El default de MySQL |
| Serializable | Todos | El más seguro y lento |
Fenómenos: lectura sucia (lees datos de una transacción sin confirmar), lectura no repetible (lees lo mismo dos veces y cambia), phantom (una fila nueva aparece en medio de una consulta).
⚠️ Terminología: commit (confirmar), rollback (deshacer), deadlock (dos transacciones se bloquean mutuamente — el motor mata a una y la rehaces), isolation (aislamiento), snapshot isolation (cada transacción ve una foto consistente de la DB — como hace PostgreSQL con MVCC).
MVCC
PostgreSQL y MySQL usan MVCC (Multi-Version Concurrency Control): cada transacción trabaja sobre una versión (snapshot) de los datos, sin bloquear a las demás. Lectores no bloquean a escritores y viceversa. Detalle completo en wiki: Transacciones.
Tipos de bases de datos
No todo es SQL. Según la forma de los datos, hay sistemas especializados:
| Tipo | Modelo | Ejemplos | Ideal para |
|---|---|---|---|
| Relacional (SQL) | Tablas con relaciones | PostgreSQL, MySQL, SQLite | Datos estructurados con relaciones, transacciones |
| Documentos (NoSQL) | JSON/BSON anidados | MongoDB, CouchDB | Datos con forma variable, prototipos |
| Clave-valor | Mapas in-memory o en disco | Redis, Memcached, DynamoDB | Caché, sesiones, contadores |
| Columnar (OLAP) | Columnas, no filas | ClickHouse, BigQuery | Analytics, agregaciones sobre millones de filas |
| Grafo | Nodos y aristas | Neo4j | Relaciones profundas (social, recomendaciones) |
| Búsqueda | Índice invertido | Elasticsearch, OpenSearch | Búsqueda full-text y logs |
| Time-series | Series temporales | TimescaleDB, InfluxDB | Métricas, sensores, monitoreo |
💡 SQL vs NoSQL: elige SQL si tus datos son relaciones y necesitas transacciones fuertes (la mayoría de los casos). Elige NoSQL cuando el shape del dato es variable, la escala exige particionado horizontal fácil, o la latencia exige in-memory. «NoSQL» significa «Not Only SQL»: no excluye SQL.
Lo que debes saber de cada uno en el plan
- PostgreSQL: el relacional de referencia del plan. En Backend: bases de datos y en Indices.
- Redis: la caché in-memory estándar (keys con TTL, estructuras, pub/sub).
- MongoDB: el documento NoSQL más popular.
- Elasticsearch: búsqueda y logs (ELK).
- SQLite: la DB embebida (archivo local, sin servidor) — perfecta para aprendizaje y prototipos.
Terminología que debes dominar
| Término | En una frase |
|---|---|
| DBMS | Sistema gestor de base de datos (el motor) |
| Query | Una consulta |
| Query planner | El que decide cómo ejecutar tu SELECT (usa índices o no) |
| EXPLAIN | Ver el plan de ejecución de una consulta |
| Índice | Estructura auxiliar (B+tree) para buscar rápido |
| Full table scan | Recorrer toda la tabla (lento) |
| Transacción | Unidad de operaciones todo-o-nada |
| Commit / Rollback | Confirmar / deshacer |
| ACID | Atomicidad, Consistencia, Aislamiento, Durabilidad |
| Normalización | Eliminar duplicados y anomalías |
| JOIN | Combinar tablas por relación |
| Agregación | COUNT, SUM, AVG, GROUP BY |
| Clave primaria / foránea | ID de la fila / referencia a otra tabla |
| Modelo (ORM) | Capa que traduce objetos ↔ SQL |
Práctica propuesta
- Instala SQLite (viene con Python:
import sqlite3). Crea una tablausuariosy haz CRUD completo. - Modela una relación muchos-a-muchos (usuarios ↔ cursos) con una tabla puente. Haz JOINs.
- Crea 2 tablas con 1.000.000 de filas (genera con un bucle). Ejecuta una búsqueda sin índice y otra con índice. Compara tiempos con
EXPLAIN QUERY PLAN. - Haz una transferencia bancaria con transacciones (
BEGIN/COMMIT/ROLLBACK). Rompe elCOMMITa mitad y verifica que el rollback funciona. - Simula una carrera de datos: dos clientes incrementan un contador a la vez. Compara con/sin transacción.
- Aprende a leer
EXPLAIN ANALYZEde una consulta: ¿usa índice o scan?
Para profundizar
- SQL, Índices, Transacciones: las wikis del sitio con detalle y ejemplos.
- Backend: bases de datos: cómo trabajar con BBDD desde un backend real.
- CMU 15-445 — Intro to Database Systems: el curso de Andy Pavlo; construyes un DBMS real (BusTub).
- Architecture of a Database System: el paper que explica las partes de un motor de BBDD.
- Berkeley CS186: introducción con proyectos de SQL.
- Use the Index, Luke: todo sobre índices, gratis y práctico.
- Sigue con 🌐 Sistemas distribuidos.