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

Что такое денормализация базы данных?

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

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

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

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

Денормализация базы данных

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

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

Нормализация — это процесс организации данных для избежания избыточности и аномалий. Классические формы нормализации:

1NF (First Normal Form) — атомарность значений, нет повторяющихся групп.

2NF (Second Normal Form) — каждый атрибут зависит от полного ключа.

3NF (Third Normal Form) — между не-ключевыми атрибутами нет транзитивных зависимостей.

Почему денормализация нужна

Полностью нормализованная БД часто требует множества JOIN операций, которые медленно работают на больших объёмах данных:

SELECT 
    u.id,
    u.name,
    COUNT(o.id) as order_count,
    AVG(oi.price * oi.quantity) as avg_order_value
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY u.id, u.name;

Этот запрос требует двух JOIN и GROUP BY. На миллионах записей это может быть медленно.

Примеры денормализации

1. Сохранение агрегированных данных

Вместо вычисления COUNT и SUM каждый раз, сохраняю их в таблице user:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100),
    order_count INT DEFAULT 0,
    total_spent DECIMAL(10, 2) DEFAULT 0
);

При добавлении заказа обновляю счётчик:

BEGIN;
INSERT INTO orders (user_id, amount) VALUES (123, 500);
UPDATE users 
SET order_count = order_count + 1,
    total_spent = total_spent + 500
WHERE id = 123;
COMMIT;

2. Дублирование часто запрашиваемых данных

CREATE TABLE order_items (
    id BIGINT PRIMARY KEY,
    order_id BIGINT,
    product_id BIGINT,
    product_name VARCHAR(255),
    product_price DECIMAL(10, 2),
    quantity INT
);

3. Кеширование в денормализованном поле

CREATE TABLE posts (
    id BIGINT PRIMARY KEY,
    title VARCHAR(255),
    content TEXT,
    author_id BIGINT,
    author_name VARCHAR(100),
    comment_count INT DEFAULT 0,
    last_comment_at TIMESTAMP
);

Когда использовать денормализацию

1. OLAP системы (Analytics)

Для аналитики требуется быстрое чтение большого количества данных:

CREATE TABLE fact_sales (
    sale_id BIGINT,
    date_id INT,
    product_id INT,
    region_id INT,
    amount DECIMAL,
    quantity INT,
    product_name VARCHAR(255),
    category_name VARCHAR(100),
    region_name VARCHAR(100),
    country_name VARCHAR(100)
);

2. Часто читаемые, редко обновляемые данные

Если таблица читается 1000 раз в день, но обновляется 1 раз в неделю — денормализация окупится.

3. Проблемы с производительностью

Если профилер показал узкое место в JOIN'е — рассматриваю денормализацию.

Когда НЕ использовать денормализацию

1. Часто обновляемые данные

Если денормализованное поле меняется часто, приходится обновлять его везде.

2. Данные переменного размера

Нельзя денормализовать список из 10000 элементов в одно поле.

Практический подход (реальный пример)

В системе подписок использовал микс нормализации и денормализации:

CREATE TABLE subscriptions (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    plan_id BIGINT,
    started_at TIMESTAMP,
    expires_at TIMESTAMP
);

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100),
    current_plan_name VARCHAR(50),
    subscription_expires_at TIMESTAMP,
    is_active BOOLEAN
);

При изменении подписки:

def update_subscription(user_id, new_plan_id):
    session.execute(
        """
        BEGIN;
        UPDATE subscriptions SET plan_id = :plan_id WHERE user_id = :user_id;
        UPDATE users SET 
            current_plan_name = (SELECT name FROM plans WHERE id = :plan_id),
            subscription_expires_at = NOW() + INTERVAL '1 month'
        WHERE id = :user_id;
        COMMIT;
        """,
        {"plan_id": new_plan_id, "user_id": user_id}
    )

Инструменты для мониторинга

Проверка консистентности денормализованных данных:

def verify_denormalization():
    real_count = db.query(
        "SELECT COUNT(*) FROM orders WHERE user_id = %s"
    ).scalar()
    
    cached_count = db.query(
        "SELECT order_count FROM users WHERE id = %s"
    ).scalar()
    
    assert real_count == cached_count, f"Mismatch: {real_count} vs {cached_count}"

Выводы

Денормализация — это мощный инструмент оптимизации, но требует осторожности:

  1. Измеряй перед оптимизацией через EXPLAIN ANALYZE
  2. Документируй причины денормализации
  3. Поддерживай консистентность через транзакции
  4. Переопредели время жизни кеша

Правильно применённая денормализация может дать 10-100x улучшение скорости для OLAP систем и часто читаемых операций.

Что такое денормализация базы данных? | PrepBro