Антинормализация: когда и зачем её использовать

Что такое антинормализация и зачем она нужна? 🏗️

Нормализация базы данных — это священный грааль проектирования, но иногда строгие правила нужно нарушать. Антинормализация — это преднамеренное отклонение от нормальных форм для повышения производительности или упрощения запросов.

Когда это оправдано:

  • Системы аналитики (OLAP)
  • Частые сложные JOIN-операции
  • Кэширование часто используемых данных
  • Упрощение API или ORM-моделей

Живой пример: отчеты vs транзакции 💼

Представим интернет-магазин. В нормализованной схеме заказ разбит на 5 таблиц:

-- Нормализованный вариант
SELECT o.order_id, c.name, p.product_name, oi.quantity
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

Для отчетного дашборда эффективнее денормализованная версия:

-- Денормализованная таблица отчетов
CREATE TABLE order_reports AS
SELECT 
    o.id AS order_id,
    c.name AS customer_name,
    p.name AS product_name,
    oi.quantity,
    o.total_amount,
    o.created_at
FROM orders o
/* остальные JOIN */

Основные техники антинормализации 🛠️

1. Дублирование данных (избыточность)

Добавляем часто запрашиваемые поля прямо в основную таблицу:

ALTER TABLE users ADD COLUMN last_order_amount DECIMAL(10,2);

Плюс: избавляемся от подзапросов для получения этой информации.

2. Вычисляемые поля

Храним результаты сложных вычислений:

ALTER TABLE products ADD COLUMN total_sales DECIMAL(12,2) 
GENERATED ALWAYS AS (price * sales_count) STORED;

3. Объединение таблиц (флэттенинг)

Сливаем 1:1 связанные таблицы в одну:

CREATE TABLE employee_profiles AS
SELECT e.*, p.bio, p.skills
FROM employees e
JOIN profiles p ON e.id = p.employee_id;

Когда НЕ стоит денормализовать? ⚠️

  1. Транзакционные системы (OLTP) — где важна целостность данных
  2. Часто изменяемые данные — синхронизация станет кошмаром
  3. Малоизвестные связи — есть риск потерять важные зависимости

Перед денормализацией всегда спрашивайте:

  • Насколько часто данные обновляются?
  • Насколько критична производительность в этом месте?
  • Можно ли решить проблему материализованными представлениями?

Лучшие практики антинормализации 🏆

1. Документируйте все изменения — пометьте денормализованные поля в схеме

2. Используйте триггеры для поддержания согласованности:

CREATE TRIGGER update_user_stats
AFTER INSERT ON orders
FOR EACH ROW
UPDATE users SET order_count = order_count + 1 
WHERE id = NEW.user_id;

3. Тестируйте на реалистичных данных — сравнивайте производительность до/после

4. Рассмотрите альтернативы — возможно, поможет правильная индексация или партиционирование


Реальный кейс: социальная сеть 🌐

Оригинальная схема:

  • users — профили
  • posts — записи
  • post_stats — счетчики лайков/просмотров (отдельная таблица)

Проблема: главная страница тормозит из-за сложных JOIN.

Решение:

-- Денормализованная таблица для ленты
CREATE TABLE feed_cache (
    post_id INT PRIMARY KEY,
    author_name VARCHAR(100),
    content TEXT,
    likes_count INT DEFAULT 0,
    /* другие часто используемые поля */
);

-- Обновляется через триггеры или джоб

Результат: время отклика сократилось с 1200ms до 80ms.

Скрыть рекламу навсегда

🧠 Учёба без воды и зубрёжки

Закрытый Boosty с наработками опытного преподавателя.

Объясняю сложное так, чтобы щелкнуло.

🚀 Забрать доступ к Boosty