Ответ
Разбиение таблиц (нормализация) — это компромисс между целостностью данных и производительностью. В моей практике я применяю нормализацию осознанно, в зависимости от требований конкретного проекта.
Когда нормализация оправдана:
- Устранение аномалий данных — когда обновление должно затрагивать только одно место
- Сложные бизнес-правила — разные сущности с разными жизненными циклами
- Частые изменения структуры — легче менять небольшие таблицы
Пример нормализованной структуры для интернет-магазина:
-- Основные таблицы
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE addresses (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
address_line TEXT NOT NULL,
city VARCHAR(100) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
address_id INT NOT NULL,
status ENUM('pending', 'processing', 'shipped', 'delivered'),
total DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (address_id) REFERENCES addresses(id)
);
Когда стоит денормализовать:
- Частые аналитические запросы — денормализация ускоряет SELECT
- Системы чтения (read-heavy) — кэширование вычисленных полей
- Микросервисная архитектура — каждый сервис владеет своими данными
Мой подход:
- Начинаю с нормализованной схемы (3NF) для обеспечения целостности
- Измеряю производительность реальных запросов
- Денормализую только проблемные места, добавляя вычисляемые поля или материализованные представления
- Документирую причины денормализации в миграциях
Например, в высоконагруженном приложении я добавлял поле full_name в таблицу пользователей, хотя имя и фамилия уже хранились отдельно, чтобы избежать CONCAT в частых запросах.
Ответ 18+ 🔞
А, ну это же классика, ебать мои старые костыли! Разбивать таблицы или не разбивать — вот в чём вопрос, прямо как у Гамлета. Слушай сюда, я тебе сейчас на пальцах объясню, как я с этим живу.
Нормализация — это, по сути, такой компромисс, понимаешь? С одной стороны, у тебя данные целые и невредимые, а с другой — чтобы всё летало, как угорелое. Я к этому подхожу не с бухты-барахты, а с холодной башкой. Для каждого проекта своя история.
Когда эту всю нормализацию стоит городить, а?
- Чтобы аномалии данные не съели. Ну представь: обновляешь адрес в одном месте, а он у тебя в двадцати заказах размножился. Это же пиздец, чувак. Надо, чтобы тыкнул в одно поле — и везде подтянулось. Чистота, блядь.
- Когда бизнес-правила сложнее, чем инструкция к китайской дрели. Разные сущности, у каждой свой жизненный цикл. Клиенты, заказы, платежи — всё отдельно, чтоб не превратить базу в свалку.
- Если структура меняется чаще, чем твоё настроение. Менять маленькую табличку — раз плюнуть. А ковыряться в одной здоровенной, где всё в кучу сгружено — это ж волнение ебать, терпения ноль.
Вот смотри, как для магазина это выглядит, если делать по уму:
-- Основные таблицы
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE addresses (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
address_line TEXT NOT NULL,
city VARCHAR(100) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
address_id INT NOT NULL,
status ENUM('pending', 'processing', 'shipped', 'delivered'),
total DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (address_id) REFERENCES addresses(id)
);
Красота, всё на своих местах. Но это, блядь, не серебряная пуля.
А когда надо на всё забить и денормализовать?
- Когда отчёты и аналитика запрашивают каждые пять минут. JOIN'ы на овердохуища таблиц — это смерть производительности. Иногда лучше продублировать данные, но чтоб SELECT'ы летали.
- Системы, где читают в сто раз чаще, чем пишут. Можно заранее посчитать и закэшировать прямо в поле таблицы — и всем хорошо.
- В этих ваших модных микросервисах. Каждый сервис тащит своё одеяло и хранит данные так, как ему удобно, а не как в учебнике по SQL написано.
Как я вообще работаю? По шагам, без паники.
- Стартую всегда с нормальной схемы. По третьей нормальной форме, чтобы голова не болела за целостность. Это фундамент, ёпта.
- Запускаю всё в бой и начинаю мерить. Смотрю, где запросы начинают хрипеть и задыхаться. Без замеров — это гадание на кофейной гуще.
- Денормализую точечно, как хирург. Не всю базу, а только конкретные проблемные места. Добавляю вычисляемое поле, материализованное представление — что по ситуации.
- Обязательно пишу, зачем это сделал. В комментарии к миграции. Через полгода сам забудешь, почему тут это поле продублировано, а без записи будешь сидеть и думать: «Какого хуя?».
Вот реальный пример: было высоконагруженное приложение, где постоянно нужно было полное имя юзера. Имя и фамилия в отдельных полях — это хорошо. Но каждый раз делать CONCAT(first_name, ' ', last_name) в тысячах запросов — это жесть. Что сделал? Добавил поле full_name и стал его обновлять триггером. Денормализация? Да. Но запросы сразу ожили. Иногда надо быть не святее папы римского, а практичнее.