Денормалізація - свідоме дублювання даних заради швидкодії читання: зберігати похідне чи скопійоване значення, щоб не обчислювати його й не з'єднувати таблиці на кожен запит.
Типові випадки:
- Лічильники:
posts.comments_countзамістьcount(*)по коментарях на кожній сторінці списку. - Агрегати:
orders.totalзамість суми по позиціях; рейтинг товару. - Копії для історії - насправді не денормалізація, а окремий факт:
order_items.priceв момент покупки, адреса доставки в замовленні. - Дані для пошуку й списків: ім'я автора в рядку статті, щоб показати список без
JOIN. - Звітні таблиці й матеріалізовані подання.
Коли виправдано: читань набагато більше, ніж записів; обчислення чи JOIN справді дорогі (виміряно, а не здається); допустима невелика затримка актуальності.
Ціна - узгодженість. Тепер одне значення живе в двох місцях, і їх треба синхронізувати. Способи:
- Тригер у базі - надійно, бо спрацює для будь-якого коду, що змінює дані:
UPDATE posts SET comments_count = comments_count + 1 WHERE id = NEW.post_id;
- У застосунку в тій самій транзакції (Laravel:
increment()поруч зі створенням коментаря). Простіше, але масові зміни в обхід моделей пропустять оновлення. - Асинхронно - подія в черзі перераховує агрегат. Допускає затримку.
- Періодичне перерахування як страховка - нічне вирівнювання лічильників з реальністю.
Конкурентність: comments_count = comments_count + 1 атомарний; а «прочитати лічильник, додати один, записати» - втрачає оновлення при паралельних запитах.
Правило: спершу нормалізована схема й індекси. Денормалізувати конкретні місця, коли профілювання показало проблему, - і разом з механізмом підтримки узгодженості, а не «потім допишемо».