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

В чём разница между нормализацией и денормализацией?

1.7 Middle🔥 251 комментариев
#Базы данных (SQL)

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

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

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

Нормализация vs Денормализация: баланс между консистентностью и производительностью

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

Нормализация: теория идеальных форм

Нормализация — это процесс организации данных в БД таким образом, чтобы минимизировать дублирование и аномалии обновления. Данные распределяются по разным таблицам согласно определённым правилам (нормальные формы).

Первая нормальная форма (1NF): Атомарность

# ❌ Не нормализовано: ненужды хранить список в одном поле
users = [
    {"id": 1, "name": "Alice", "skills": "Python, JavaScript, SQL"},
    {"id": 2, "name": "Bob", "skills": "Java, C++"},
]

# ✅ Нормализовано: каждое значение атомарно
users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
skills = [
    {"user_id": 1, "skill": "Python"},
    {"user_id": 1, "skill": "JavaScript"},
    {"user_id": 1, "skill": "SQL"},
    {"user_id": 2, "skill": "Java"},
    {"user_id": 2, "skill": "C++"},
]

Вторая нормальная форма (2NF): Полная функциональная зависимость

# ❌ Не нормализовано: department_name зависит от department_id, а не от employee_id
employees = [
    {"employee_id": 1, "name": "Alice", "department_id": 10, "department_name": "Engineering"},
    {"employee_id": 2, "name": "Bob", "department_id": 10, "department_name": "Engineering"},
]
# Проблема: дублирование department_name (аномалия при обновлении)

# ✅ Нормализовано: каждая таблица имеет свой primary key
employees = [
    {"employee_id": 1, "name": "Alice", "department_id": 10},
    {"employee_id": 2, "name": "Bob", "department_id": 10},
]
departments = [
    {"department_id": 10, "department_name": "Engineering"},
]

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

# ❌ Не нормализовано: city_postal_code зависит от city, а не от address_id
addresses = [
    {"address_id": 1, "street": "123 Main St", "city": "New York", "postal_code": "10001"},
    {"address_id": 2, "street": "456 Park Ave", "city": "New York", "postal_code": "10001"},
]

# ✅ Нормализовано: city → postal_code в отдельной таблице
addresses = [
    {"address_id": 1, "street": "123 Main St", "city_id": 1},
    {"address_id": 2, "street": "456 Park Ave", "city_id": 1},
]
cities = [
    {"city_id": 1, "city": "New York", "postal_code": "10001"},
]

Преимущества нормализации

1. Экономия памяти

  • Нет дублирования данных
  • Меньше занимает дискового пространства

2. Целостность данных (Data Integrity)

# В нормализованной БД легче обеспечить ACID свойства
# Обновляем значение один раз — оно обновляется везде
UPDATE departments SET name = "Engineering Team" WHERE id = 10;
# Все сотрудники этого отдела видят обновлённое значение

3. Упрощение обновлений

  • Обновляем данные в одном месте
  • Меньше вероятность аномалий обновления

Недостатки нормализации

1. Множество JOIN операций

# Медленный запрос: нужны JOINы для получения всех данных
SELECT e.name, d.name, c.city, c.postal_code
FROM employees e
JOIN departments d ON e.department_id = d.id
JOIN addresses a ON e.address_id = a.id
JOIN cities c ON a.city_id = c.id
WHERE e.employee_id = 1;

# Проблема: 4 JOINа для простого запроса → медленно

2. Сложность запросов

  • Запросы становятся сложными и трудными для оптимизации

Денормализация: практический компромисс

Денормализация — это процесс намеренного добавления дублирования данных в БД для улучшения производительности за счёт повышения сложности обновления.

# ❌ Нормализованная схема (медленнее)
orders table:
  id, user_id, total_price

order_items table:
  id, order_id, product_id, quantity, price

users table:
  id, name, email

# Запрос: получить все заказы пользователя с его именем и общей стоимостью
SELECT o.id, u.name, SUM(oi.price * oi.quantity) as total
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id;

# ✅ Денормализованная схема (быстрее)
orders table:
  id, user_id, user_name, user_email, total_price

order_items table:
  id, order_id, product_id, product_name, quantity, price

# Запрос: просто читаем готовые данные
SELECT id, user_name, total_price FROM orders;

Преимущества денормализации

1. Быстрые читаемые операции (SELECT)

  • Меньше JOINов
  • Готовые агрегированные значения

2. Простые запросы

# Простой и быстрый запрос
SELECT user_name, total_price FROM orders WHERE id = 1;

Недостатки денормализации

1. Аномалии обновления

# Проблема: обновляем имя пользователя
UPDATE users SET name = "Alice Smith" WHERE id = 1;

# Но в таблице orders всё ещё старое имя!
# Нужно обновлять везде:
UPDATE orders SET user_name = "Alice Smith" WHERE user_id = 1;
UPDATE order_items SET ... WHERE order_id IN (SELECT id FROM orders WHERE user_id = 1);
# Скрытые ошибки и несогласованность

2. Больше памяти

  • Дублирование данных занимает больше места

3. Сложность поддержки

  • Нужно синхронизировать данные в нескольких местах
  • Выше вероятность ошибок

Когда что использовать

Нормализация подходит:

  • Системы с частыми обновлениями (OLTP — Online Transaction Processing)
  • Когда консистентность критична
  • Маленькие и средние БД

Денормализация подходит:

  • Системы с частыми чтениями (OLAP — Online Analytical Processing)
  • Data Warehouses, аналитические системы
  • Когда производительность критична
  • Кэширующие слои (Redis, Memcached)

Практический подход: гибридное решение

# Используем нормализованную таблицу как source of truth
users = [{"id": 1, "name": "Alice", "email": "alice@example.com"}]
orders = [{"id": 100, "user_id": 1, "total": 500}]

# Но создаём денормализованное представление для быстрых читаемых операций
# VIEW (SQL)
CREATE VIEW user_orders AS
SELECT o.id, o.total, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id;

# Или кэшируем денормализованные данные
import redis
rc = redis.Redis()
rc.set(f"order:100", json.dumps({"id": 100, "total": 500, "user_name": "Alice"}))

# Когда обновляется пользователь — инвалидируем кэш
rc.delete("order:100")

Заключение

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