Нормализация против денормализации

Урок противопоставляет два стиля проектирования таблиц и связывает их с типом нагрузки.

Нормализация — разбиение данных на таблицы без дублей. Денормализация — сознательное дублирование данных ради скорости чтения.

Два мира: OLTP и OLAP

В транзакционных системах (OLTP — заказы, платежи) важна целостность: каждый факт хранится один раз, чтобы не было противоречий. Это нормализация. В аналитических системах (OLAP — отчёты) важна скорость чтения больших объёмов, и ради неё данные денормализуют — дублируют контекст прямо в строках.

СвойствоНормализация (OLTP)Денормализация (OLAP)
Цельцелостность, нет дублейскорость чтения
Записьчастая, быстраяпакетная
Чтениеточечноемассовые агрегаты
JOIN-овмногомало

Как работает под капотом

Покажем разницу на одном примере. Нормализованный вариант: заказы и клиенты в отдельных таблицах, имя клиента — только в customers.

CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, city TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);
INSERT INTO customers VALUES (1,'Анна','Москва'), (2,'Борис','Казань');
INSERT INTO orders VALUES (1,1,300), (2,1,500), (3,2,700);

-- чтобы получить город по заказам, нужен JOIN
SELECT c.city, SUM(o.amount) AS revenue
FROM orders o
JOIN customers c ON c.id = o.customer_id
GROUP BY c.city;

Денормализованный вариант для аналитики: город уже лежит в строке заказа, JOIN не нужен.

CREATE TABLE orders_flat (id INTEGER PRIMARY KEY, city TEXT, amount INTEGER);
INSERT INTO orders_flat VALUES (1,'Москва',300), (2,'Москва',500), (3,'Казань',700);

SELECT city, SUM(amount) AS revenue
FROM orders_flat
GROUP BY city;

Оба запроса дают одинаковый ответ, но второй проще: нет JOIN, движку не нужно сопоставлять таблицы по ключу. На трёх строках разница незаметна, а на сотнях миллионов JOIN превращается в дорогую операцию, и денормализация экономит секунды, а то и минуты на каждом запросе аналитика. Поэтому витрины для дашбордов почти всегда денормализованы — их строит конвейер один раз за ночь, а читают потом тысячи раз за день.

Чем платим за денормализацию

Дублирование означает, что при изменении (Анна переехала) нужно обновить много строк. Поэтому денормализованные таблицы строит конвейер — он пересобирает их из нормализованных источников по расписанию, и проблема обновления решается перестроением витрины.

Ключевая мысль: нормализация и денормализация — не «правильно» и «неправильно», а инструменты под разные задачи. Транзакционная база приложения (где Анна реально меняет город в профиле) должна быть нормализованной, чтобы не было противоречий. Аналитическая витрина (где мы считаем выручку по городам) выигрывает от денормализации. Дата-инженер живёт на стыке: он читает нормализованный источник и строит из него денормализованную витрину — этим и занимается шаг Transform в ETL.

Частые ошибки

  • Денормализовать OLTP-базу приложения. Дубли в транзакционной базе ведут к рассинхрону и багам; там нужна нормализация.
  • Гнаться за «третьей нормальной формой» в аналитике. Идеально нормализованная аналитическая модель медленна из-за JOIN-ов.
  • Обновлять денормализованную витрину точечно. Её проще целиком пересобрать конвейером, чем латать отдельные строки.

Итог

  • Нормализация (OLTP) хранит факт один раз ради целостности.
  • Денормализация (OLAP) дублирует контекст ради скорости чтения и меньшего числа JOIN-ов.
  • Денормализованные витрины пересобирает конвейер по расписанию.
Проверьте себя
1. Для какой нагрузки уместна нормализация?
AДля аналитических отчётов с массовыми агрегатами
BДля транзакционных систем (OLTP), где важна целостность и нет дублей
CТолько для файлов CSV
DНормализация нигде не нужна
2. Чем выгодна денормализация для аналитики?
AЭкономит место на диске
BУменьшает число JOIN-ов и ускоряет массовые агрегатные запросы
CГарантирует отсутствие дублей
DУскоряет запись отдельных строк