← Назад к вопросам

Что такое Денормализация в БД?

1.7 Middle🔥 201 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Денормализация в БД: баланс между нормализацией и производительностью

Денормализация — это преднамеренное нарушение принципов нормализации БД для улучшения производительности. Это контролируемое добавление избыточных данных в БД, которые могут быть получены из других таблиц через JOIN, но вычисляются один раз и хранятся для быстрого доступа.

Напоминание: Нормализация

Нормализация — принцип проектирования БД, который устраняет избыточность и зависимости:

❌ ДЕнормализованная таблица (плохо):
┌─────┬──────────┬──────────┬────────┬──────────────┐
│ ID  │ Заказ    │ Клиент   │ Город  │ Телефон      │
├─────┼──────────┼──────────┼────────┼──────────────┤
│ 1   │ Order001 │ Иван     │ Москва │ +7 (495) ... │
│ 2   │ Order002 │ Иван     │ Москва │ +7 (495) ... │ <- Дублирование!
└─────┴──────────┴──────────┴────────┴──────────────┘

✅ Нормализованная (правильно):
Таблица orders:          Таблица customers:
┌────────┬───────────┐   ┌────────┬──────────┬────────┬──────────┐
│ ID     │ CustomerID│   │ ID     │ Name     │ City   │ Phone    │
├────────┼───────────┤   ├────────┼──────────┼────────┼──────────┤
│ Order001│ 1        │   │ 1      │ Иван     │ Москва │ +7...    │
│ Order002│ 1        │   └────────┴──────────┴────────┴──────────┘
└────────┴───────────┘

Когда применяется денормализация?

Денормализация применяется, когда нормализация создаёт проблемы производительности:

1. Множество JOINов

-- ❌ Слишком много JOINов (медленно)
SELECT 
    o.id, 
    c.name, 
    c.city,
    p.product_name,
    cat.category_name,
    p.price
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
JOIN categories cat ON p.category_id = cat.id
WHERE o.customer_id = 42;

-- ✅ Денормализованная версия (быстро)
SELECT 
    id,
    customer_name,
    customer_city,
    product_name,
    category_name,
    price
FROM denormalized_orders
WHERE customer_id = 42;

2. Дорогостоящие вычисления

-- ❌ Вычисляется каждый раз (медленно)
SELECT 
    c.id,
    c.name,
    COUNT(o.id) as order_count,
    SUM(o.total) as total_spent,
    AVG(o.total) as avg_order
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id;

-- ✅ Денормализация: храним статистику заранее
ALTER TABLE customers ADD COLUMN (
    order_count INT,
    total_spent DECIMAL,
    avg_order DECIMAL,
    updated_at TIMESTAMP
);

-- Быстрое получение
SELECT id, name, order_count, total_spent, avg_order
FROM customers
WHERE id = 42;

3. Часто читаемые данные

-- Денормализуем часто читаемые поля
ALTER TABLE posts ADD COLUMN (
    author_name VARCHAR(100),      -- из users
    author_avatar_url VARCHAR(255), -- из users
    comment_count INT,             -- из comments
    like_count INT,                -- из likes
    last_comment_at TIMESTAMP      -- из comments
);

-- Теперь одна таблица вместо 4+ JOINов
SELECT * FROM posts ORDER BY last_comment_at DESC LIMIT 10;

Типичные денормализации

1. Копирование полей

-- Копируем email из users в orders для быстрого доступа
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    user_email VARCHAR(255),  -- Дублирование! (денормализация)
    total DECIMAL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

2. Кэширование агрегатов

-- Вместо COUNT(*) FROM comments WHERE post_id = X
CREATE TABLE posts (
    id INT PRIMARY KEY,
    title VARCHAR(255),
    comment_count INT DEFAULT 0,  -- Кэшируем количество
    like_count INT DEFAULT 0       -- Кэшируем количество лайков
);

-- Обновляем при изменениях
UPDATE posts SET comment_count = comment_count + 1 WHERE id = 123;

3. Материализованные представления (Materialized Views)

-- Дорогостоящий аналитический запрос
CREATE MATERIALIZED VIEW customer_stats AS
SELECT 
    c.id,
    c.name,
    COUNT(DISTINCT o.id) as order_count,
    SUM(o.total) as total_revenue,
    MAX(o.created_at) as last_order_date
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id;

-- Индексируем для быстрого доступа
CREATE INDEX idx_customer_stats ON customer_stats(id);

-- Обновляем периодически (например, каждый час)
REFRESH MATERIALIZED VIEW customer_stats;

4. Денормализация для текстового поиска

-- Вместо поиска по 10 таблицам
CREATE TABLE search_index (
    id INT,
    type ENUM('user', 'post', 'comment'),
    title VARCHAR(255),
    content TEXT,
    author_name VARCHAR(100),
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    FULLTEXT INDEX (title, content, author_name)
);

-- Быстрый поиск по всему
SELECT * FROM search_index WHERE MATCH(title, content) AGAINST('+database -sql');

Стратегии денормализации

1. Redis/Memcached кэш

// Вместо денормализации в БД, используем кэш
std::string getCachedUserStats(int user_id) {
    // Пытаемся из Redis
    auto cached = redis.get("user:" + std::to_string(user_id) + ":stats");
    if (cached) return cached;
    
    // Если нет, вычисляем
    auto stats = calculateStats(user_id);  // Дорого
    redis.setex("user:" + std::to_string(user_id) + ":stats", 3600, stats);
    return stats;
}

2. Event Sourcing

-- Вместо хранения состояния, храним события
CREATE TABLE order_events (
    id INT PRIMARY KEY,
    order_id INT,
    event_type ENUM('created', 'paid', 'shipped', 'delivered'),
    timestamp TIMESTAMP,
    data JSON
);

-- Вычисляем состояние из событий (Event Sourcing)
-- Денормализуем в отдельную таблицу для быстрого доступа
CREATE TABLE order_state (
    id INT PRIMARY KEY,
    status ENUM('pending', 'paid', 'shipped', 'delivered'),
    last_event_id INT
);

3. CQRS (Command Query Responsibility Segregation)

Write side (normalized):       Read side (denormalized):
  ┌─────────────┐               ┌────────────┐
  │   orders    │────→ sync ───→│ orders_view│
  │  customers  │               │ customers  │
  │  products   │               │ stats      │
  └─────────────┘               └────────────┘
  Сложная логика            Готовые данные

Преимущества и недостатки

Преимущества денормализации:

  • Быстрые чтения (меньше JOINов)
  • Меньше нагрузка на CPU и память
  • Проще запросы
  • Лучше для OLAP (аналитика)

Недостатки денормализации:

  • Сложное обновление (нужно синхронизировать несколько таблиц)
  • Больше дисковое пространство
  • Риск несогласованности данных
  • Нужен код для синхронизации (триггеры, приложение)
  • Сложнее логика операций обновления

Правило большого пальца

Нормализуй по умолчанию, денормализуй по необходимости.

  1. Спроектируй нормализованную схему
  2. Напиши запросы на основе требований
  3. Измерь производительность (EXPLAIN ANALYZE)
  4. Если медленно — денормализуй стратегически
  5. Профилируй после каждого изменения

Итог

Денормализация — это не зло, а инструмент оптимизации. В большинстве приложений нужна комбинация нормализованного ядра БД и стратегической денормализации для производительности. Для backend разработчика важно понимать trade-offs и выбирать правильно для конкретного use case.