Проектирование и оценка

​

Как использовать JSONB в PostgreSQL

Создано 15.10.2026

Обновлено 15.10.2026

Чем JSONB отличается от JSON, когда выбирать колонки или EAV и как проверять индексы. Примеры PostgreSQL, ограничения, обновления и план испытаний.

Короткий ответ

JSONB — тип PostgreSQL для хранения JSON в разобранном двоичном представлении. Он удобен для изменяемых свойств, настроек и документов, которые читаются как единое целое. По содержимому можно выполнять запросы и создавать индексы, но это не отменяет проектирование структуры данных.

Обычно полезна гибридная модель: идентификаторы, связи, статусы и основные поля остаются в обычных колонках, а переменное содержимое — в JSONB. Выбор проверяют на реальных фильтрах, сортировках, отчётах и обновлениях. Один GIN-индекс не ускоряет любой запрос, а корректный JSON не гарантирует правильность документа.

Ниже — сравнение JSON и JSONB, учебный пример, выбор индексов и порядок проверки решения. Результат для команды — границы JSON-документа, контракт значений и измеренный набор запросов, по которому можно согласовать хранение.

Чем JSONB отличается от JSON

JSON — формат обмена данными. В PostgreSQL json и jsonb — два разных типа хранения этого формата. Первый сохраняет текстовое представление, второй разбирает документ при записи и сохраняет структуру для последующей обработки. Не следует смешивать JSON как внешний формат и json как конкретный тип БД.

Критерийjsonjsonb

Представление

Сохраняет входной JSON-текст

Хранит разобранную структуру

Пробелы и порядок ключей объекта

Сохраняются в исходном представлении

Не сохраняются как свойство исходного текста

Повторяющиеся ключи объекта

Сохраняются; функции обработки используют последнее значение

Остаётся последнее значение

Обработка содержимого

Требуется повторный разбор текста

Повторный синтаксический разбор не нужен

Индексация

Возможны индексы подходящих выражений

Есть поддержка операторов JSONB и GIN

Типовая причина выбора

Нужно сохранить JSON-текст

Нужно регулярно обрабатывать и искать содержимое

Для обработки содержимого PostgreSQL обычно рекомендует JSONB. Но из этого не следует, что JSONB всегда меньше, быстрее записывается или выигрывает во всех запросах. Разбор при вводе имеет стоимость, а объём хранения и скорость зависят от документа, индексов и нагрузки. Отличия описаны в документации PostgreSQL 18 по JSON-типам.

Если нужно подтвердить точные байты полученного сообщения, сохраните исходное тело отдельно в подходящем виде, например как bytea, вместе с метаданными получения. Даже сохранение JSON-текста не заменяет фиксацию исходного HTTP-тела, его кодировки и подписи. После нормализации и преобразования в JSONB восстановить прежние пробелы и порядок ключей нельзя.

Что JSONB не проверяет

Тип обеспечивает корректность принимаемого JSON-представления с ограничениями PostgreSQL, но не знает, что у насоса обязательна мощность, что её единица — кВт и что отрицательное значение недопустимо. Он также допускает массив или скаляр, если это отдельно не ограничено. Проверка структуры объекта и прикладная проверка его свойств — разные задачи.

SQL NULL, JSON null и отсутствие ключа следует различать. Оператор ->> может вернуть SQL NULL и для отсутствующего свойства, и для JSON null. Если различие важно, проверяйте наличие ключа оператором ? и JSON-значение через ->. Не сводите три состояния к пустой строке при импорте.

Когда выбирать JSONB, колонки или EAV

Начните с операций над объектом. Какие поля участвуют в связях, что изменяется независимо, какие ограничения нужно гарантировать в БД и по каким значениям будут регулярные отчёты? Сам факт появления новых атрибутов ещё не делает JSONB единственным выбором.

СценарийПредпочтительный ориентирЧто уточнить

Идентификатор, владелец, статус, дата

Обычные типизированные колонки

Уникальность, обязательность, права и жизненный цикл

Отношение с другим объектом

Внешний ключ или отдельная таблица связи

Удаление, история, кратность и ссылочная целостность

Небольшое переменное содержимое карточки

JSONB с согласованной структурой

Типы свойств, версии и частота обновлений

Управляемый справочник атрибутов и независимые значения

EAV с определениями и типизированными значениями

Применимость, единицы, кратность и основные выборки

Частые числовые отчёты и сортировки

Колонка или проверенное индексируемое выражение

Преобразования типов, статистика и цена поддержки индекса

В EAV значение является отдельной записью, связанной с определением атрибута. В JSONB переменные свойства обычно собраны в документе строки. У EAV и JSONB различается организация обновлений, запросов, правил и истории; универсальная замена одного другим не обоснована. Базовые критерии и объяснение модели приведены в статье про динамические атрибуты данных.

JSONB особенно удобен, когда документ имеет понятную границу и часто читается целиком: настройки подключения, параметры обработки, дополнительные характеристики. Если две части постоянно изменяют разные процессы, объединение их в один большой документ способно создать конкуренцию за одну строку. В таком случае стоит пересмотреть границы объектов.

Вынос свойства в колонку также не является поражением гибкой модели. Часто изменяемое содержимое и несколько устойчивых полей для поиска существуют рядом. Если данные дублируются, заранее определите источник истины и способ синхронизации. Иначе ускорение запросов может сопровождаться расхождением значений.

Как читать JSONB и подбирать индексы

Учебная таблица оборудования

Продолжим условный пример насоса и ёмкости. Инвентарный номер и категория лежат в обычных колонках, технические свойства — в attributes. Номер схемы относится к контракту этих свойств. Числа и названия учебные; код демонстрирует операции, а не результат измерения производительности.

CREATE TABLE equipment (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  inventory_number text NOT NULL UNIQUE,
  category text NOT NULL,
  schema_version integer NOT NULL,
  attributes jsonb NOT NULL DEFAULT '{}'::jsonb,
  CHECK (jsonb_typeof(attributes) = 'object')
);

INSERT INTO equipment
  (inventory_number, category, schema_version, attributes)
VALUES
  ('P-17', 'pump', 1,
   '{"power_kw": 2.2, "flow_m3h": 4.5}'),
  ('T-18', 'tank', 1,
   '{"volume_l": 300}');

NOT NULL запрещает SQL NULL в столбце, а jsonb_typeof(...)= 'object' ограничивает верхний уровень объектом. Эта проверка не делает мощность обязательной и не проверяет её диапазон. У всех строк примера версия равна 1, но разные категории могут использовать разные определения контракта, выбранные приложением по категории и версии.

-- -> возвращает JSONB, ->> возвращает text
SELECT inventory_number,
       attributes -> 'power_kw' AS power_json,
       attributes ->> 'power_kw' AS power_text
FROM equipment
WHERE category = 'pump';

-- Вхождение с сохранением числового типа
SELECT inventory_number
FROM equipment
WHERE attributes @> '{"power_kw": 2.2}'::jsonb;

Не путайте число 2.2 и строку "2.2": в запросе на вхождение это разные JSON-значения. -> сохраняет JSON-представление, ->> извлекает текст. Для числового сравнения нужен числовой тип, а не лексикографическое сравнение строк. Операторы описаны в руководстве PostgreSQL по JSON-функциям.

Индекс выбирается под выражение и оператор

-- Поиск по вхождению в разные части документа
CREATE INDEX equipment_attributes_gin
  ON equipment USING gin (attributes);

-- Равенство / диапазон / сортировка по одному числу
CREATE INDEX equipment_power_idx
  ON equipment (((attributes ->> 'power_kw')::numeric));

SELECT inventory_number
FROM equipment
WHERE (attributes ->> 'power_kw')::numeric >= 2
ORDER BY (attributes ->> 'power_kw')::numeric;

Индекс по числовому выражению предполагает, что все непустые значения power_kw приводятся к numeric. Если в существующих документах лежит строка «неизвестно», создание индекса или запись может завершиться ошибкой. Очистите данные и обеспечьте контракт до создания такого индекса. Отсутствующее свойство даёт NULL; решение о его допустимости остаётся в правилах модели.

ЗапросЧто рассмотретьОграничение

Вхождение через @>

GIN по документу или нужному поддокументу

Нужно проверять селективность и цену записи

Наличие ключа через ?

GIN с классом jsonb_ops

jsonb_path_ops не поддерживает операторы существования ключей

Числовой диапазон или сортировка

B-tree по числовому выражению либо обычной колонке

Выражение запроса должно подходить индексу; значения должны приводиться к типу

Чтение объекта по id

Первичный ключ

GIN не нужен только ради получения одной строки

jsonb_ops и jsonb_path_ops поддерживают разные наборы операторов и имеют разные свойства размера и поиска. Вариант path_ops часто полезен для поддерживаемых запросов на вхождение, но не является универсальной оптимизацией. Проверяйте конкретный запрос через EXPLAIN; базовые правила индексов по выражениям также применимы.

Как проверять значения и обновлять документ

Определите контракт содержимого: обязательные свойства, типы, единицы, неизвестные поля и версию. Для JSON-документов можно применять JSON Schema. Проверка на API полезна, но нужно учесть импорт, фоновые задания, административные операции и прямые записи в БД. Наличие схемы в репозитории само по себе не означает, что все эти пути её используют.

Устойчивые локальные ограничения стоит закреплять в БД, если это оправдано: NOT NULL, CHECK, UNIQUE и внешние ключи для выделенных отношений. Небольшой CHECK может защитить форму документа, однако обычный CHECK не заменяет полноценный валидатор произвольной JSON Schema. Для проверки схемы внутри PostgreSQL потребуется отдельное выбранное решение; встроенный тип JSONB этого не делает.

Не забывайте, что CHECK считается выполненным и при результате SQL NULL. Если выражение проверяет извлечённое свойство, отсутствие ключа может не дать отказа. Обязательность нужно описать и проверить явно. Это особенно важно, когда команда переносит правило из приложения в SQL и ожидает те же результаты для неполных документов.

Изменение одного свойства

UPDATE equipment
SET attributes = jsonb_set(
  attributes, '{power_kw}', '2.5'::jsonb
)
WHERE inventory_number = 'P-17';

jsonb_set меняет значение по указанному пути в результате выражения, но это не обещание физически изменить только несколько байтов в хранилище. Обновление создаёт новую версию строки по правилам PostgreSQL и получает блокировку строки. Большие документы, индексы и частые изменения необходимо проверять вместе.

Обновление на сервере одним SQL-выражением отличается от чтения JSON в приложение и последующей записи всего документа. Во втором случае параллельные изменения могут быть потеряны без блокировки или контроля версии. Выберите подходящий механизм конкурентного изменения и проверьте конфликт двух писателей. Граница атомарного документа должна соответствовать бизнес-операции.

Изменение типа, единиц и обязательности требует проверки старых строк и потребителей. Сохраните идентификатор контракта и прикладную версию, подготовьте миграцию и переходный период. Порядок управления версиями разобран в руководстве по реестру схем. Не подменяйте миграцию автоматическим заполнением default во всех старых документах без решения владельца данных.

Как проверить решение до внедрения

  1. Зафиксируйте операции и ограничения. Открытие карточки, фильтр по двум свойствам, сортировка, отчёт, импорт, обновление и удаление должны иметь ожидаемый результат.
  2. Подготовьте представительную выборку. Включите редкие категории, пропущенные ключи, null, числовые строки, большие документы и неравномерное распределение значений.
  3. Сравните варианты на одной нагрузке. Проверяйте JSONB, подходящие колонки и при необходимости EAV с корректной типизацией и индексами. Сравнение с заведомо неиндексированной моделью не доказывает общий выигрыш.
  4. Измерьте чтение и запись. Используйте планы, время, объём данных и индексов, конкуренцию обновлений. EXPLAIN ANALYZE выполняет запрос, поэтому эксперименты с изменениями проводите на тестовых данных.
  5. Согласуйте эксплуатацию и изменения. Опишите владельца схемы, проверки каждого пути записи, миграции и критерии возврата к другой модели.

Проверка маленького примера подтверждает синтаксис и смысл операторов, но не производительность будущей системы. Важны реальные распределения, размеры, конкуренция, конфигурация PostgreSQL и состояние статистики. Порядок чтения плана приведён в документации EXPLAIN.

Типовые ошибки и итоговый пакет

  • Сохранять все отношения и основные фильтры в JSONB только ради гибкости.
  • Создать GIN и ожидать ускорения любого выражения с ->>.
  • Сравнивать числовые значения как текст или допускать несовместимые типы в одном свойстве.
  • Считать JSONB достаточной проверкой структуры и бизнес-правил.
  • Не учитывать цену индексов, объём обновлений и конкуренцию за одну строку.

На выходе нужны описание границы документа, карта обычных и динамических полей, контракт значений, проверенные SQL-запросы и планы, набор индексов и правила конкурентного обновления. Для каждого важного свойства укажите, почему оно находится в колонке или JSONB и какое наблюдение заставит пересмотреть выбор.

Если существующие данные уже распределены по нескольким системам, добавьте сверку и план переноса из руководства по миграции данных. Архитектурное решение должно сопровождаться проверкой сохранённых значений, а не только новой формой таблицы.

Что дальше

Выберите один объект и выпишите его основные запросы. Если сначала нужно разделить устойчивые поля и переменные свойства, начните с модели динамических атрибутов. Затем согласуйте контракт JSON Schema и проверьте индексы на ожидаемой нагрузке.

Обсудить проект

Если хотите применить этот материал к вашему проекту, напишите нам. Поможем уточнить вводные, риски и следующий шаг: оценку, discovery, разработку, интеграцию или сопровождение.

Связаться