Зачем моделировать данные: цена плохой модели
В прошлом модуле мы договорились, что модель данных — это чертёж между реальностью и базой. Теперь разберём этот тезис строго: что конкретно происходит без чертежа, во что обходится плохая модель и почему усилия на моделирование окупаются. Это не философия — это про реальные баги, медленные запросы и сорванные сроки, с которыми сталкивается любая команда.
Моделирование как инвестиция
Моделирование данных стоит времени на старте проекта. Соблазн пропустить его велик: «нарисуем таблицы по ходу, надо быстрее запускаться». Поэтому важно понять, что моделирование — это инвестиция: небольшие затраты сейчас экономят большие затраты потом.
Цена изменения схемы растёт со временем нелинейно. Поправить чертёж на этапе проектирования — минуты. Изменить структуру таблицы, когда приложение уже написано, но данных мало — часы плюс правки кода. Перестроить схему, когда в базе миллионы строк и десяток сервисов от неё зависят — это многонедельный проект с миграцией данных, риском простоя и потери данных.
Это и есть экономический аргумент за моделирование: вы оплачиваете дешёвую работу сейчас, чтобы не оплачивать дорогую потом.
Что даёт хорошая модель: четыре свойства
Хорошая модель данных обеспечивает четыре конкретных свойства. Когда говорят «модель плохая», обычно нарушено одно из них.
Целостность (integrity). Данные не противоречат друг другу и правилам предметной области. У товара одна цена, а не три разных. Заказ не может ссылаться на несуществующего клиента. Количество товара не отрицательное. Хорошая модель делает невозможные состояния невозможными — база физически не даёт записать противоречие.
Отсутствие избыточности. Каждый факт хранится ровно один раз. Email клиента — в одном месте. Это не про экономию диска (диск дёшев) — это про то, что один экземпляр факта невозможно рассинхронизировать сам с собой.
Производительность нужных запросов. Модель проектируется под те запросы, которые система будет делать. Для OLTP это быстрая запись и точечное чтение, для OLAP — быстрые агрегации. Запросы, которые система делает часто, должны быть дёшевы.
Расширяемость. Систему можно развивать, не ломая существующее. Добавить скидки, новый тип оплаты, ещё одну страну — без переписывания половины базы.
| Свойство | Что нарушается без него | Пример сбоя |
|---|---|---|
| Целостность | Данные противоречивы | Заказ ссылается на удалённого клиента |
| Нет избыточности | Дубли рассинхронизируются | Два разных email у одного человека |
| Производительность | Запросы медленные | Отчёт строится 4 минуты |
| Расширяемость | Изменение ломает старое | Новое поле требует переписать 20 запросов |
Цена плохой модели: аномалии вблизи
Главная конкретная цена плохой модели — аномалии. В прошлом модуле мы их назвали, теперь посмотрим вплотную на SQL. Возьмём плоскую таблицу, где смешаны данные о сотрудниках и отделах:
CREATE TABLE staff (
emp_id INTEGER,
emp_name TEXT,
dept_name TEXT,
dept_budget INTEGER
);
INSERT INTO staff VALUES
(1, 'Анна', 'Маркетинг', 500000),
(2, 'Борис', 'Маркетинг', 500000),
(3, 'Виктор', 'Финансы', 800000);
Бюджет отдела «Маркетинг» записан дважды — в строках Анны и Бориса. Это избыточность, и она порождает три аномалии.
Аномалия обновления. Бюджет маркетинга вырос до 600000. Корректный апдейт:
UPDATE staff SET dept_budget = 600000 WHERE dept_name = 'Маркетинг';
Если же кто-то по ошибке обновит только одну строку:
UPDATE staff SET dept_budget = 600000 WHERE emp_id = 1;
-- теперь у отдела Маркетинг два разных бюджета: 600000 и 500000
База приняла этот UPDATE без возражений — она не знает, что бюджет отдела должен быть один. Данные стали противоречивыми, и никакой запрос больше не даст достоверного ответа «каков бюджет маркетинга».
Аномалия вставки. Открыли отдел «Логистика» с бюджетом, но сотрудников туда ещё не наняли. Куда записать отдел? Строка таблицы — это сотрудник:
INSERT INTO staff VALUES (NULL, NULL, 'Логистика', 300000);
-- приходится ставить NULL в emp_id и emp_name — строка-сотрудник без сотрудника
Получается строка с пустым сотрудником. Это не запись об отделе — это испорченная запись о сотруднике. Факт «существует отдел Логистика» негде хранить чисто.
Аномалия удаления. Виктор — единственный в отделе «Финансы». Виктор уволился:
DELETE FROM staff WHERE emp_id = 3;
-- вместе с Виктором исчез сам факт существования отдела Финансы и его бюджета
Удалив сотрудника, мы потеряли несвязанный факт — отдел и его бюджет. База опять не возразила.
Обратите внимание: во всех трёх случаях СУБД выполнила команду без ошибки. Плохая модель не «ломается» с сообщением об ошибке — она тихо допускает порчу данных. Ошибку обнаруживают потом, когда отчёт показывает чушь, и долго ищут причину. Хорошая модель, наоборот, не дала бы выполнить противоречивую операцию.
Решение — разнести данные по двум таблицам: departments (отдел и бюджет, каждый отдел один раз) и employees (сотрудник со ссылкой на отдел). Тогда бюджет хранится в одном месте, отдел заводится независимо от сотрудников, а увольнение последнего сотрудника не трогает запись об отделе. Как делать это системно — тема модуля про нормализацию.
Аномалия — это нарушение инварианта
Поднимемся на уровень выше. Инвариант — это правило предметной области, которое должно соблюдаться всегда: «у отдела ровно один бюджет», «у заказа ровно один клиент». Хорошая модель кодирует инварианты в саму структуру так, что нарушить их невозможно.
В плохой схеме инвариант «у отдела один бюджет» не закодирован — он существует только в голове разработчика. Поэтому база не может его защитить. Аномалия — это и есть момент, когда инвариант нарушился, а база не имела средств этому помешать.
Вот почему нормализация — это не про «красоту» схемы. Это про корректность: правильная декомпозиция превращает инвариант в структурное свойство, и СУБД начинает его охранять автоматически.
Данные как актив: ещё один угол
Мы говорили, что данные — актив, чья ценность зависит от структуры. Добавим практический угол: на данных принимают решения. Руководитель смотрит отчёт о выручке и решает, какой товар закупать. Если в основе отчёта плохая модель с рассинхронизированными дублями, отчёт врёт — и решение принимается по ложным данным.
Стоимость плохой модели здесь — не «медленный запрос», а неправильное бизнес-решение. Это переводит моделирование из разряда «технические детали для разработчиков» в разряд «то, от чего зависит, можно ли доверять данным компании вообще».
Есть и обратная сторона: иногда избыточность добавляют сознательно — это денормализация. В аналитических хранилищах продублировать атрибут ради скорости чтения — нормальное инженерное решение. Разница в том, что денормализация делается осознанно, с пониманием цены, и под контролем — а не случайно. Этому посвящён отдельный урок модуля про нормализацию.
Хорошая модель и скорость разработки
Есть ещё одно следствие хорошей модели, которое часто недооценивают: она ускоряет не только запросы, но и саму разработку. Когда структура данных продумана, новые функции приложения ложатся на неё естественно. Нужно добавить отзывы к товарам — заводится таблица reviews со ссылкой на товар и клиента, и всё. Существующий код не трогается.
В плохо смоделированной базе всё иначе. Каждая новая функция — это борьба со структурой: данные лежат не там, где нужно, приходится их дублировать, писать обходные запросы, добавлять костыли. Разработка замедляется, и чем дальше, тем сильнее. Команды называют это «технический долг в данных» — и проценты по нему платятся каждым спринтом.
Шесть измерений качества данныхПоэтому моделирование — это вложение не только в корректность, но и в скорость команды. Хорошая модель — это фундамент, на котором быстро строится. Плохая — это болото, в котором каждый шаг даётся с усилием.
Признак того, что модель начала мешать: простые на первый взгляд задачи («добавить поле», «показать ещё один отчёт») регулярно оказываются неожиданно сложными. Если это происходит часто — проблема, скорее всего, не в исполнителях, а в модели данных под ними.
Попробуй сам
Создайте у себя плоскую таблицу staff из этого урока и наполните её данными. Затем своими руками воспроизведите все три аномалии: выполните «частичный» UPDATE одной строки и убедитесь запросом SELECT DISTINCT dept_name, dept_budget FROM staff, что у одного отдела появилось два бюджета; попробуйте вставить отдел без сотрудника; удалите единственного сотрудника отдела и убедитесь, что отдел исчез. После этого спроектируйте на бумаге две таблицы — departments и employees — и объясните себе словами, почему в новой схеме каждая из трёх аномалий становится невозможной. Сохраните этот разбор: это прямая подготовка к лабе по нормализации.