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

Расскажи про свой опыт работы с нормализацией

1.3 Junior🔥 171 комментариев
#Опыт и проекты#Базы данных и SQL

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

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

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

Мой опыт работы с нормализацией базы данных

Нормализация — одна из ключевых навыков системного аналитика, особенно когда проектируешь схему базы данных. За 10+ лет я видел красивые нормализованные БД и ужасные денормализованные. Поделюсь практическим опытом.

Что такое нормализация

Нормализация — это процесс организации данных в таблицы таким образом, чтобы:

  • Минимизировать дублирование
  • Обеспечить целостность данных
  • Облегчить обновление и удаление данных
  • Улучшить производительность запросов

Есть несколько уровней нормализации, от 1NF до 5NF. На практике обычно достаточно 3NF.

Примеры из моего опыта

Пример 1: Проект управления школой

Начальная (денормализованная) схема:

Табли students:

  • id, name, age, class, teacher_name, teacher_phone, subject1_name, subject1_grade, subject2_name, subject2_grade

Проблемы:

  • Если имя учителя изменится, нужно обновить все строки с этим учителем
  • Данные учителя дублируются для каждого ученика
  • Сложно добавить 3-й предмет (нужно менять схему таблицы)
  • Запросы "все ученики учителя Иванова" требуют LIKE %Иванов%

Нормализованная схема (1NF и выше):

table teachers
- id (PK)
- name
- phone

table classes
- id (PK)
- name
- teacher_id (FK -> teachers.id)

table students
- id (PK)
- name
- age
- class_id (FK -> classes.id)

table subjects
- id (PK)
- name

table student_grades
- id (PK)
- student_id (FK -> students.id)
- subject_id (FK -> subjects.id)
- grade

Результат:

  • Изменили имя учителя в одном месте
  • Учителя выбирается по ID, не по имени
  • Легко добавить новый предмет (просто добавляем строку в student_grades)
  • Запрос "все ученики учителя" прост и быстр

Пример 2: Интернет-магазин

Денормализованная версия:

table orders
- id, customer_name, customer_email, customer_phone, customer_address,
  product1_name, product1_price, product1_quantity,
  product2_name, product2_price, product2_quantity,
  ...
  total_amount, order_date

Проблемы:

  • Если цена товара изменилась, в старом заказе она остается неправильной
  • Если данные клиента изменились (переехал), сложно это отследить
  • Максимум товаров в заказе ограничен схемой таблицы
  • Невозможно получить "все заказы этого клиента" без парсинга данных
  • Повторение данных клиента для каждого заказа

Нормализованная версия (3NF):

table customers
- id, name, email, phone, address, created_at

table orders
- id, customer_id (FK), order_date, total_amount, status

table order_items
- id, order_id (FK), product_id (FK), quantity, unit_price

table products
- id, name, price, category, stock

Результат:

  • Исторические данные сохраняются (unit_price в order_items)
  • Обновление цены продукта не влияет на старые заказы
  • Обновление данных клиента происходит в одном месте
  • Неограниченное количество товаров в заказе
  • Легко запросить "все заказы клиента #123"
  • Легко найти заказы по дате, сумме, статусу

Пример 3: Социальная сеть (Facebook-like)

Денормализованная (плохая) идея:

table users
- id, name, friends_list (JSON: [1,2,3,4,5])
- followers_list (JSON: [10,20,30])

Проблемы:

  • Добавить друга = обновить JSON в users и в друге
  • Race condition: два человека добавляют друг друга одновременно
  • Сложно запросить "все друзья пользователя #123"
  • Нельзя индексировать массив эффективно
  • Хранение занимает много места (дублирование)

Нормализованная версия:

table users
- id, name, created_at

table friendships
- id
- user_id_1 (FK -> users.id)
- user_id_2 (FK -> users.id)
- status (PENDING, ACCEPTED, BLOCKED)
- created_at
- unique constraint (user_id_1, user_id_2) чтобы не было дубликатов

table followers
- id
- follower_id (FK -> users.id)
- following_id (FK -> users.id)
- created_at

Результат:

  • Добавить друга = вставить одну строку
  • Нет race conditions (база данных обрабатывает)
  • Запрос "все друзья" просто: SELECT * FROM friendships WHERE user_id_1=123
  • Индексы работают (индекс на user_id_1)
  • Каждая связь хранится один раз
  • Легко добавить новые статусы (BLOCKED, MUTED и т.д.)

Когда я денормализую (сознательно)

Нормализация хороша, но иногда нужна денормализация для производительности.

Пример 4: E-commerce с высокой нагрузкой

Ситуация: Есть миллиарды заказов. Каждый раз при загрузке статуса заказа нужно:

SELECT o.*, c.name, c.email, (SELECT COUNT(*) FROM order_items WHERE order_id=o.id) as item_count
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.id = 123

Это медленно при высокой нагрузке.

Денормализация (контролируемая):

ALTER TABLE orders ADD COLUMN customer_name VARCHAR(255);
ALTER TABLE orders ADD COLUMN customer_email VARCHAR(255);
ALTER TABLE orders ADD COLUMN item_count INT;

Тогда запрос становится:

SELECT * FROM orders WHERE id = 123

Много быстрее! Но нужно:

  • При изменении имени клиента, обновить также в orders (triggers)
  • Или пересчитывать денормализованные поля периодически (batch job)
  • Документировать что это денормализация и почему

Компромисс: Денормализуем для чтения (SELECT), остаемся нормализованным для записи (INSERT/UPDATE).

Форвады нормализации, которые я видел

Проблема 1: Избыточная нормализация

Ситуация: Аналитик создал таблицу для каждого атрибута.

table user_names { user_id, first_name }
table user_emails { user_id, email }
table user_ages { user_id, age }
...

Запрос "все данные пользователя" требует 10 JOINов!

Решение: Оставить в одной таблице users простые атрибуты. Нормализация нужна для сложных отношений (orders, addresses, etc), не для примитивов.

Проблема 2: Игнорирование историчности

Ситуация: Табла customers с address. Если клиент переедет, старый address теряется.

Решение: Добавить таблицу customer_addresses с historical data:

table customers
- id, name, current_address_id, created_at

table customer_addresses
- id, customer_id, address, is_current, from_date, to_date

Как я подхожу к нормализации в проектах

Шаг 1: Выявляю сущности и их отношения

Например: customers, orders, products

Customer 1---* Order
Order *---* Product

Шаг 2: Проверяю на аномалии

  • Update anomaly: обновить адрес = обновить все его заказы?
  • Delete anomaly: удалить заказ = потерять информацию о клиенте?
  • Insert anomaly: добавить нового клиента = добавить фиктивный заказ?

Шаг 3: Применяю нормальные формы

  • 1NF: атомарные значения, нет массивов
  • 2NF: нет partial dependencies
  • 3NF: нет transitive dependencies
  • Обычно 3NF достаточно

Шаг 4: Проверяю производительность

  • Если требуется JOINы 10 таблиц для простого запроса — переосмысляю
  • Если индексы становятся неэффективны — денормализую контролируемо

Шаг 5: Документирую

Объясню почему именно эта структура. Если есть денормализация, объясню почему и как поддерживать целостность.

Лучшие практики

  1. Начните с нормализации Проектируйте нормально, потом денормализуйте если нужно для производительности.

  2. Используйте инструменты dbdiagram.io, Lucidchart или просто SQL для создания диаграмм.

  3. Думайте про целостность данных Foreign keys, constraints, не оставляйте логику целостности в приложении.

  4. Документируйте решения Почему эта таблица связана с той, почему она денормализована.

  5. Пересматривайте по мере роста То что было хорошо для 1M записей, может быть плохо для 1B.

Вывод

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

Расскажи про свой опыт работы с нормализацией | PrepBro