JavaRush /Курси /SQL SELF /Баланс між нормалізацією та продуктивністю

Баланс між нормалізацією та продуктивністю

SQL SELF
Рівень 26 , Лекція 3
Відкрита

Коли ми ідеально нормалізуємо дані, кожна таблиця стає максимально компактною, і інформація в ній відповідає лише одному принципу. Але для виконання реальних запитів (наприклад, "Які студенти записані на курс SQL?") може знадобитися об'єднати купу таблиць. Чим більше таблиць, тим складніше запити, тим більше система "копає лопатою".

Напевно тобі вже знайомі JOIN-и з попередніх лекцій. Ось приклад запиту, який може знадобитися при роботі з нормально спроєктованою базою даних:

SELECT students.name, courses.title
FROM students
JOIN enrollments ON students.id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.id
WHERE courses.title = 'SQL';

Звучить просто, але під капотом сервер виконує адську роботу: читає кожну таблицю, з'єднує дані, фільтрує... А що, якщо таблиці дуже-дуже великі? Логічно припустити, що продуктивність буде падати.

Війни: нормалізація проти швидкості

На щастя (чи на жаль?), у реальному житті бази даних — це компроміс. Повна нормалізація забезпечує цілісність даних, але сповільнює виконання складних запитів. Якщо база даних буде використовуватись для аналітики та звітів, то іноді вигідніше денормалізувати її. Це як замінити 10 маленьких коробочок на одну велику скриню: діставати дані швидше, але розкласти їх заново стане складніше.

Коли варто "видихнути" з нормалізацією?

Є сценарії, де денормалізація краща:

Часто використовувані агрегати

Наприклад, уяви, що система щодня робить запити на підрахунок кількості студентів на кожному курсі. У нормалізованій структурі доведеться виконувати JOIN і COUNT() постійно. Замість цього можна додати в таблицю "Courses" колонку student_count, яка оновлюється автоматично при додаванні/видаленні записів.

-- Денормалізований стовпець
UPDATE courses
SET student_count = (
    SELECT COUNT(*)
    FROM enrollments
    WHERE enrollments.course_id = courses.id
);

Часто виконувані звіти

Якщо твій клієнт щодня хоче звіт "Хто, де, коли купував?", простіше зберігати денормалізовану таблицю з готовими рядками типу "Ім'я клієнта, товар, дата". Основна таблиця буде більшою, але швидкість отримання даних зросте.

Великі обсяги читання, малі обсяги запису

Коли база даних в основному використовується для читання (наприклад, аналітики), варто пожертвувати нормалізацією заради швидкості.

Мінімізація з'єднань при складних зв'язках

Якщо між таблицями багатошарові (nested) зв'язки, і JOIN стає кошмаром, прибери деякі рівні нормалізації.

Приклад: як денормалізація прискорює роботу?

Маємо нормалізовані таблиці інтернет-магазину:

Таблиця products Таблиця orders Таблиця order_items
id id id
name date order_id
price customer_id product_id
quantity

Кожне замовлення (orders) складається з рядків замовлення (order_items). Порахуємо, скільки грошей заробив магазин:

SELECT SUM(order_items.quantity * products.price) AS total_revenue
FROM order_items
JOIN products ON order_items.product_id = products.id;

Процес з'єднання order_items і products сповільнить запит на великих обсягах даних.

Денормалізована структура

Тепер уяви, що в таблиці order_items є "зайва" колонка total_price (денормалізація):

Таблиця order_items
id
order_id
product_id
quantity
total_price

Тепер запит стає тривіальним:

SELECT SUM(total_price) AS total_revenue
FROM order_items;

Так ми уникаємо JOIN, а значить прискорюємо виконання.

Практичне завдання: оптимізація бази "Продажі"

Дано: нормалізовані таблиці

Таблиця products Таблиця sales
id id
name product_id
price date
quantity

Задача: прискорити часті запити типу "Скільки всього заробили на кожному продукті?".

Крок 1: додамо колонку total_price у таблицю sales:

ALTER TABLE sales ADD COLUMN total_price NUMERIC;

Крок 2: заповнимо цю колонку для існуючих даних:

UPDATE sales
SET total_price = quantity * (
    SELECT price
    FROM products
    WHERE products.id = sales.product_id
);

Крок 3: роби запити швидше:

SELECT product_id, SUM(total_price) AS total_revenue
FROM sales
GROUP BY product_id;

Але! У денормалізації є ціна

Ти ж розумієш, що "швидше" не завжди означає "краще". У випадку денормалізації з'являються проблеми:

Надлишкове сховище

Колонка total_price — це копія даних, яка потребує додаткового місця.

Складність оновлень

Якщо ціна продукту зміниться в таблиці products, ми маємо оновити відповідну колонку total_price вручну. Це може призвести до невідповідностей.

Аномалії при вставці, оновленні та видаленні

Інформація легко "розсинхронізується", якщо забути оновити денормалізовані дані. Наприклад, якщо змінюється ціна продукту, це відбувається не автоматично.

Баланс: як знайти золоту середину?

Вибери, що важливіше: продуктивність чи структура? Якщо база активно читається, підлаштовуйся під запити.

Додавай денормалізацію точково. Наприклад, лише для ключових чисел і звітів.

Автоматизуй оновлення денормалізованих даних. Використовуй тригери або задачі, щоб уникнути невідповідностей.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ