Практически в каждом проекте рано или поздно возникает вопрос:
Стоит ли хранить эти данные в JSONB или лучше создать отдельные таблицы?
Допустим, у товара появились характеристики.
Можно сделать так:
products
id
name
attributes jsonb{
"color": "red",
"weight": 250,
"country": "Germany"
}А можно сделать нормализованную структуру.
products
attributes
product_attributesКакой вариант лучше?
Ответ не такой очевидный.
Когда JSONB действительно хорош
JSONB отлично подходит, если структура данных заранее неизвестна.
Например:
- настройки пользователя;
- параметры интеграций;
- ответ внешнего API;
- произвольные поля CRM;
- данные маркетплейсов.
Например:
{
"ozon": {
"sku": 123,
"warehouse": 55
},
"wb": {
"barcode": "123456789"
}
}Создавать десятки таблиц ради подобных данных обычно бессмысленно.
Когда JSONB начинает мешать
Представим, что нам нужно получить:
все товары красного цвета тяжелее 200 граммов.
Запрос выглядит так:
SELECT *
FROM products
WHERE
attributes->>'color' = 'red'
AND
(attributes->>'weight')::int > 200;Работать это будет.
Но возникает сразу несколько вопросов.
- Как индексировать?
- Как обеспечить типизацию?
- Как проверить обязательность поля?
- Как сделать внешний ключ?
- Как написать JOIN?
С каждым новым условием запрос становится сложнее.
Отдельные таблицы
Та же модель может выглядеть так.
products
product_specs
product_id
color
weight
countryЗапрос становится обычным SQL.
SELECT *
FROM products p
JOIN product_specs s
ON s.product_id = p.id
WHERE
s.color = 'red'
AND
s.weight > 200;Индексируется проще.
Планировщик понимает типы данных.
Работают ограничения.
Работают внешние ключи.
Производительность
Многие считают, что JSONB всегда быстрее.
Это неправда.
Все зависит от нагрузки.
JSONB выигрывает:
- при редких выборках;
- при неизвестной структуре;
- при хранении документов.
Отдельные таблицы выигрывают:
- при сложной аналитике;
- при большом количестве JOIN;
- при агрегировании;
- при частой фильтрации.
Индексы
JSONB поддерживает GIN.
CREATE INDEX idx_products_attributes
ON products
USING gin(attributes);Но это не означает, что любые запросы станут быстрыми.
Например
attributes @> '{"color":"red"}'использует индекс.
А вот
(attributes->>'weight')::int > 200обычно уже нет.
Приходится делать expression index.
CREATE INDEX idx_weight
ON products (((attributes->>'weight')::int));Получается, что индекс становится привязан к конкретному полю.
Если таких полей десятки — преимуществ JSONB становится меньше.
Изменение схемы
Аргумент в пользу JSONB:
Не нужны миграции.
Это действительно удобно.
Сегодня записали:
{
"color": "red"
}Завтра:
{
"color": "red",
"material": "steel"
}Никаких ALTER TABLE.
Но есть обратная сторона.
Через год можно обнаружить, что часть записей содержит
weightдругая
Weightтретья
productWeightИ база уже никак это не контролирует.
Хороший компромисс
Во многих проектах отлично работает гибридная модель.
Основные поля:
id
name
price
weight
category_id— отдельные колонки.
Редкие свойства:
{
"manufacturer_code": "...",
"custom_fields": ...
}— JSONB.
Получается и быстро, и гибко.
Практическое правило
Можно пользоваться простой эвристикой.
Используйте JSONB, если:
- структура заранее неизвестна;
- поля редко участвуют в поиске;
- данные приходят из внешних сервисов;
- важна гибкость.
Используйте отдельные таблицы, если:
- данные участвуют в JOIN;
- по ним часто фильтруют;
- нужны внешние ключи;
- важна строгая схема;
- требуется аналитика.
Итог
JSONB — мощный инструмент PostgreSQL, но он не заменяет реляционную модель.
Если поле становится частью бизнес-логики, по нему строятся индексы, выполняются фильтрации и соединения, его обычно стоит вынести в отдельную таблицу или колонку.
JSONB лучше рассматривать не как альтернативу нормализации, а как дополнение к ней. Лучшие схемы данных на практике часто сочетают оба подхода: реляционные таблицы для структурированных данных и JSONB для гибких, редко используемых или внешних атрибутов.


Добавить комментарий