Цена хаоса: когда плохая схема убивает бизнес
Прежде чем учить нормальные формы — поймём, зачем это нужно в реальной жизни.
Кейс: интернет-магазин «МегаМаркет»
Представьте: небольшой интернет-магазин ведёт все заказы в одной таблице orders_flat:
| order_id | customer_email | customer_city | product | qty | discount | store |
|---|---|---|---|---|---|---|
| 1001 | ivan@mail.ru | Москва | iPhone 15 | 1 | 10% | МегаЦентр |
| 1001 | ivan@mail.ru | Москва | AirPods | 2 | 10% | МегаЦентр |
| 1002 | ivan@mail.ru | Питер | MacBook | 1 | 5% | МегаСевер |
На первый взгляд всё работает. Но через год:
Инцидент 1: Двойная скидка (потери — 2 млн ₽)
Маркетолог обновляет скидку для заказа 1001:
UPDATE orders_flat SET discount = '15%' WHERE order_id = 1001;
Обновилась только первая строка. Вторая строка того же заказа — iPhone и AirPods — имеют разные скидки. Система начисляет скидку дважды и неправильно. Клиенты получают неверные чеки. Финансовый отдел обнаруживает это через три месяца.
Причина: один заказ — несколько строк. Обновление частичное.
Инцидент 2: Потеря клиента (данные удалены безвозвратно)
Клиент просит удалить все свои заказы (GDPR):
DELETE FROM orders_flat WHERE customer_email = 'ivan@mail.ru';
Удалены все строки. Вместе с ними — история продаж, которая нужна бухгалтерии. И адрес доставки, который нужен логистике для закрытия спора.
Причина: данные о клиенте и данные о заказе хранятся вместе.
Инцидент 3: Невозможно добавить магазин
Открывается новый магазин «МегаВосток» в Екатеринбурге. Но внести его в базу нельзя — таблица orders_flat хранит магазины только через заказы. Без заказа — нет строки. Нет строки — нет магазина.
Причина: сущность «магазин» не существует независимо.
Три типа аномалий
Все три инцидента — проявления классических аномалий:
| Аномалия | Инцидент | Потери |
|---|---|---|
| Обновления | Двойная скидка | Финансовые |
| Удаления | Потеря истории | Операционные |
| Вставки | Нельзя добавить магазин | Бизнес-процессные |
Что дала бы нормализация?
Правильная схема разделила бы данные по сущностям:
customers— покупателиstores— магазиныorders— заказы (один заказ = одна строка)order_items— позиции заказа
Тогда:
- Скидка хранится в одном месте — в
orders - Удаление покупателя не трогает финансовую историю
- Магазин создаётся независимо от заказов
Именно это мы будем строить в этом модуле — шаг за шагом.
Прочитайте урок до конца — прогресс засчитается автоматически