Skip to content
SP StackPractices
intermediate Por StackPractices

Desnormalización de Bases de Datos — Cuándo y Cómo Romper las Formas Normales

Guía práctica de desnormalización de bases de datos: cuándo intercambiar almacenamiento por rendimiento de lectura, patrones comunes y cómo mantener datos derivados consistentes.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

La desnormalización es la introducción intencional de redundancia en un esquema de base de datos para mejorar el rendimiento de lectura. Mientras que la normalización elimina la redundancia para garantizar la consistencia, la desnormalización acepta duplicación controlada para reducir JOINs, simplificar consultas y acelerar lecturas. No es una excusa para un mal diseño; es una optimización deliberada aplicada después de establecer una base normalizada.

When to Use

  • Cargas de trabajo con alta frecuencia de lectura donde los JOINs son el cuello de botella
  • Consultas agregadas que se ejecutan frecuentemente sobre grandes conjuntos de datos
  • Dashboards o analítica en tiempo real requieren respuesta sub-segundo
  • Existe un mecanismo confiable para mantener datos derivados sincronizados
  • El almacenamiento es más barato que la computación o la latencia

Common Patterns

PatronDescripcionEjemplo
Columnas computadasAlmacenar valores derivados junto a los datos fuentetotal_price = quantity * unit_price
ContadoresMantener conteos pre-agregadospost.like_count actualizado en cada like
Documentos embebidosAnidar datos relacionados en una sola fila/documentoOrden con items embebidos
Tablas de lookupDuplicar datos de referencia frecuentemente unidosNombre de categoria en la fila del producto
Vistas materializadasResultados de consulta pre-computados refrescados periodicamenteResumen diario de ingresos

Ejemplo de Tabla de Contadores

-- En lugar de contar likes en cada lectura
SELECT COUNT(*) FROM post_likes WHERE post_id = 123;

-- Desnormalizar en un contador
CREATE TABLE posts (
    post_id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    like_count INT NOT NULL DEFAULT 0
);

-- Actualizar contador transaccionalmente
BEGIN;
    INSERT INTO post_likes (post_id, user_id) VALUES (123, 456);
    UPDATE posts SET like_count = like_count + 1 WHERE post_id = 123;
COMMIT;

Ejemplo de Columna Computada

CREATE TABLE order_items (
    item_id SERIAL PRIMARY KEY,
    order_id INT NOT NULL REFERENCES orders(order_id),
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL,
    -- Columna computada desnormalizada
    line_total DECIMAL(10,2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);

-- La consulta ahora es un simple SELECT sin calculo
SELECT item_id, product_id, line_total FROM order_items WHERE order_id = 100;

Vista Materializada para Agregaciones

CREATE MATERIALIZED VIEW daily_revenue AS
SELECT
    DATE(created_at) as day,
    SUM(total) as revenue,
    COUNT(*) as order_count
FROM orders
GROUP BY DATE(created_at);

-- Refrescar en un horario o trigger
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue;

-- Indexar la vista materializada para lecturas rapidas
CREATE INDEX idx_daily_revenue_day ON daily_revenue(day);

Mantener Datos Desnormalizados Consistentes

EstrategiaProsContras
Actualizaciones a nivel aplicacionSimple, explicitoRiesgo de inconsistencia si multiples apps escriben
Triggers de base de datosGarantizado en cada escrituraDificil de depurar, puede ralentizar escrituras
CDC (Change Data Capture)Desacoplado, asincronoConsistencia eventual, overhead de infraestructura
Refresco de vistas materializadasAutomatico para agregadosDatos obsoletos entre refrescos
-- Trigger para mantener contador desnormalizado
CREATE OR REPLACE FUNCTION update_post_like_count()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE posts SET like_count = like_count + 1 WHERE post_id = NEW.post_id;
    ELSIF TG_OP = 'DELETE' THEN
        UPDATE posts SET like_count = like_count - 1 WHERE post_id = OLD.post_id;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER post_likes_trigger
AFTER INSERT OR DELETE ON post_likes
FOR EACH ROW EXECUTE FUNCTION update_post_like_count();

Common Mistakes

  • Desnormalizar prematuramente — normalizar primero, desnormalizar solo cuando el rendimiento lo demanda
  • Sin estrategia de sincronizacion — datos desnormalizados que divergen causan bugs silenciosos
  • Sobredesnormalizar — cada columna agregada es una carga de mantenimiento; justificar cada una
  • Ignorar amplificacion de escritura — mas campos desnormalizados significan mas escrituras por operacion
  • Tratar desnormalizacion como fix para malas consultas — optimizar consultas e indices antes de introducir redundancia

FAQ

La desnormalizacion viola la integridad de datos? No si se gestiona correctamente. La clave es tener una unica fuente de verdad (tablas normalizadas) y tratar campos desnormalizados como datos derivados/cache con mecanismos de sincronizacion claros.

Debo desnormalizar en SQL o NoSQL? Las bases NoSQL frecuentemente fomentan la desnormalizacion por diseno (documentos embebidos). En SQL, usala con moderacion y documenta la razon de cada campo desnormalizado.

Como detecto desviacion en datos desnormalizados? Ejecutar controles de consistencia periodicos: comparar el valor desnormalizado contra un calculo fresco de las tablas fuente. Alertar si divergen.