Перейти к основному содержимому

JSONB

Разработчику Аналитику Архитектору Инженеру

JSONB (Binary JSON) — тип данных PostgreSQL для хранения структурированных документов в формате JSON в разобранном бинарном виде. При записи СУБД парсит текст, нормализует представление (убирает лишние пробелы, дубли ключей), упорядочивает ключи объекта и строит дерево значений, по которому быстро читают и индексируют поля.

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

Смежные темы: PostgreSQL, основы SQL, практикум PostgreSQL по JSONB, бинарные форматы обмена (MessagePack, BSON — другой контекст).


Что такое JSONB

JSONB — специализированный тип столбца в PostgreSQL (и совместимых форках, например Arenadata Postgres). Значение логически совпадает с JSON-документом: объекты, массивы, строки, числа, true/false, null. Физически на диске и в памяти это не копия исходной строки, а компактное бинарное дерево с быстрым доступом к ключам и элементам.

Типичные задачи:

  • поле payload, metadata, settings без десятков nullable-колонок под каждый новый ключ API;
  • аудит и события: сохранить тело webhook или ответ внешней системы целиком;
  • конфигурация фич-флагов и A/B-параметров на уровне строки;
  • полуструктурированные каталоги (атрибуты товара различаются по категориям).

Для пошаговых запросов с EXPLAIN и индексами — Практикум PostgreSQL по JSONB.


Что означает "binary" (бинарный)

Слово binary здесь не означает "зашифровано" и не то же самое, что Protobuf на wire. Имеется в виду внутреннее представление в СУБД:

АспектТип json (текст)Тип jsonb (бинарный)
На дискеUTF-8 текст, как прислалиРазобранное дерево + служебные структуры
При SELECTКаждый раз разбор строкиЧтение уже готового дерева
Пробелы и форматированиеСохраняютсяОтбрасываются при записи
Порядок ключей в объектеСохраняетсяНе сохраняется (нормализация)
Дубли ключей в одном объектеПоследний не всегда однозначен при парсингеОстаётся одно значение на ключ

Пользователь и приложение по-прежнему видят JSON: клиенты получают текст через ::text, драйверы сериализуют в JSON. "Бинарность" — деталь реализации PostgreSQL для скорости и индексов.


Почему это не "просто JSON"

JSON в экосистеме веба — текстовый стандарт (RFC 8259): файл, тело HTTP, лог. В PostgreSQL тип json — это тоже текстовая строка с проверкой синтаксиса: быстрая запись "как пришло", медленнее аналитика по вложенным полям.

JSONB платит за парсинг при INSERT/UPDATE, зато:

  • ускоряет WHERE по ключам и вложенным значениям;
  • поддерживает GIN-индексы (см. Сложные индексы);
  • даёт богатый набор операторов @>, ?, ->, ->> без ручного LIKE по тексту.

Если документ только сохраняют и отдают целиком без поиска по полям — иногда достаточно json. Если фильтруют, обновляют отдельные ключи, строят отчёты — почти всегда выбирают jsonb.


JSON vs JSONB в PostgreSQL

Параметрjsonjsonb
Формат храненияТекстовая строкаБинарное дерево
Скорость записиБыстрее (без полного разбора)Медленнее (парсинг и нормализация)
Скорость чтения по полямМедленнее (разбор при каждом обращении)Быстрее
ИндексацияПрактически не для поиска по ключамGIN, B-tree по выражениям
Порядок ключейСохраняетсяНе сохраняется
Пробелы, форматированиеСохраняютсяУдаляются
Дубли ключейКак в исходной строкеСхлопываются при записи

Оба типа принимают одинаковый литерал в SQL; различие проявляется после сохранения и в планах запросов.


Синтаксис в PostgreSQL

Объявление столбца и литерал:

CREATE TABLE orders (
id BIGINT PRIMARY KEY,
payload JSONB NOT NULL,
tags JSONB DEFAULT '[]'::jsonb
);

INSERT INTO orders (id, payload)
VALUES (
1,
'{"status": "paid", "total": 1990.50, "items": [{"sku": "A1", "qty": 2}]}'::jsonb
);

Приведение типов:

SELECT '{"a": 1}'::jsonb;
SELECT payload::text FROM orders; -- текст JSON для экспорта
SELECT to_jsonb(row(id, payload)) FROM orders;

Проверка валидности (для текста до приведения):

SELECT '{"ok": true}'::json; -- OK
-- SELECT '{bad}'::jsonb; -- ошибка синтаксиса

Как выглядит в коде и в SQL

В SQL значение jsonb при выводе выглядит как обычный JSON (без гарантии порядка ключей):

SELECT payload FROM orders WHERE id = 1;
-- {"items": [{"qty": 2, "sku": "A1"}], "status": "paid", "total": 1990.5}

В приложении (псевдокод) передают строку или объект; драйвер PostgreSQL сам приводит к jsonb:

import json
import psycopg

payload = {"status": "paid", "tags": ["vip", "b2b"]}
cur.execute(
"INSERT INTO orders (id, payload) VALUES (%s, %s::jsonb)",
(1, json.dumps(payload)),
)
// Npgsql: параметр с типом JsonDocument или string + ::jsonb в SQL
await cmd.ExecuteNonQueryAsync();
// INSERT INTO orders (id, payload) VALUES (@id, @payload::jsonb)
// node-pg: объект сериализуется в JSONB
await pool.query(
'INSERT INTO events (payload) VALUES ($1)',
[{ type: 'click', meta: { page: '/home' } }]
);

На уровне HTTP API наружу по-прежнему отдают JSON; JSONB живёт в слое БД.


Где применяется

СценарийЗачем JSONB
ИнтеграцииСохранить сырой ответ партнёра, потом искать по external_id
События и аналитикаГибкая схема полей события без миграции на каждый новый ключ
Настройки пользователя / тенантаРазные наборы ключей в одной таблице
Каталоги и PIMАтрибуты товара зависят от категории
Аудитold_value / new_value как документы, diff через SQL

Не путать с BSON в MongoDB: там другая СУБД и формат на диске. JSONB — реляционный PostgreSQL + SQL.

Эксплуатация кластера: Практикум PostgreSQL 8.11.


Возможности JSONB

  • Извлечение вложенных полей: ->, ->>, #>, #>>
  • Содержимое и ключи: @>, <@, ?, ?|, ?&
  • Изменение документа: jsonb_set, jsonb_insert, ||, -, #-
  • Разбор на строки: jsonb_each, jsonb_array_elements, jsonb_populate_record
  • Агрегация в документ: jsonb_agg, jsonb_object_agg, jsonb_build_object
  • Сравнение и diff: IS DISTINCT FROM, jsonb_strip_nulls
  • Индексы GIN и B-tree по выражению (col->>'field')

Проектирование таблиц и нормализацию см. Основы баз данных.


Пример: сложный вложенный объект

INSERT INTO customers (id, profile) VALUES (
100,
'{
"name": "Анна",
"contacts": {
"email": "anna@example.com",
"phones": [{"type": "mobile", "number": "+79001234567"}]
},
"preferences": {
"locale": "ru-RU",
"notifications": {"email": true, "push": false}
},
"loyalty": {"tier": "gold", "points": 4200}
}'::jsonb
);

SELECT
profile->>'name' AS name,
profile->'contacts'->>'email' AS email,
profile->'preferences'->'notifications'->>'push' AS push_enabled,
(profile->'loyalty'->>'points')::int AS points
FROM customers
WHERE id = 100;

Обновление вложенного ключа без перезаписи всего документа:

UPDATE customers
SET profile = jsonb_set(
profile,
'{preferences,notifications,push}',
'true'::jsonb,
true
)
WHERE id = 100;

Пример: сложные массивы

Документ с массивом объектов и вложенными массивами:

INSERT INTO shipments (id, data) VALUES (
7,
'{
"route": ["MSK", "SPB", "MSK"],
"parcels": [
{
"id": "p1",
"weight_kg": 1.2,
"contents": [{"sku": "BOOK-01", "qty": 1}]
},
{
"id": "p2",
"weight_kg": 0.4,
"contents": [{"sku": "CABLE-9", "qty": 2}, {"sku": "ADAPTER-1", "qty": 1}]
}
]
}'::jsonb
);

-- развернуть посылки в строки
SELECT s.id, parcel
FROM shipments s,
jsonb_array_elements(s.data->'parcels') AS parcel
WHERE s.id = 7;

-- найти отгрузку, где есть товар с заданным sku
SELECT id
FROM shipments
WHERE data->'parcels' @> '[{"contents": [{"sku": "CABLE-9"}]}]';

Агрегация строк в JSON-массив (обратная операция):

SELECT order_id,
jsonb_agg(jsonb_build_object('sku', sku, 'qty', qty) ORDER BY sku) AS lines
FROM order_lines
GROUP BY order_id;

Фильтры в WHERE

Базовые шаблоны (подробнее с планами — в практикуме):

-- равенство текста вложенного поля
SELECT * FROM events
WHERE payload->>'event_type' = 'purchase';

-- числовое сравнение (обязательно приведение типа)
SELECT * FROM events
WHERE (payload->>'amount')::numeric > 1000;

-- несколько условий по разным ключам
SELECT * FROM events
WHERE payload->'user'->>'country' = 'RU'
AND (payload->'flags'->>'beta')::boolean IS TRUE;

-- проверка типа значения в документе
SELECT * FROM events
WHERE jsonb_typeof(payload->'meta') = 'object';

Для тяжёлых отчётов сочетают JSONB с оптимизацией SQL и корректными индексами.


Операторы "содержит" и "существует"

ОператорСмыслПример
@>левый документ содержит правый (подструктура)payload @> '{"status":"paid"}'
<@левый вложен в правый'{"a":1}' <@ col
?есть ключ верхнего уровняpayload ? 'error_code'
?|есть любой из ключейpayload ?| array['a','b']
?&есть все ключиpayload ?& array['a','b']
->значение по ключу / элемент массива (тип jsonb)payload->'items'
->>то же, как текстpayload->>'status'
#>путь как массив текстовpayload #> '{user,id}'
#>>путь, результат текстpayload #>> '{user,id}'

Примеры:

-- заказ содержит позицию с product_id 123
SELECT * FROM orders
WHERE items @> '[{"product_id": 123}]'::jsonb;

-- в событии есть ключ error_code
SELECT * FROM events
WHERE payload ? 'error_code';

-- есть любой из ключей retry или dlq
SELECT * FROM queue
WHERE message ?| array['retry', 'dlq'];

Оператор @> хорошо стыкуется с GIN (jsonb_ops или jsonb_path_ops) — см. раздел ниже и Сложные индексы.


Функции PostgreSQL для JSON и JSONB

ФункцияНазначение
jsonb_build_object, jsonb_build_arrayсобрать документ в SQL
jsonb_set, jsonb_insertизменить значение по пути
jsonb_agg, jsonb_object_aggагрегаты в JSON
jsonb_each, jsonb_each_textключи и значения в строки
jsonb_array_elementsэлементы массива
jsonb_strip_nullsубрать ключи со значением null
to_jsonb(anyelement)привести скаляр или строку к jsonb
jsonb_prettyформатированный текст для отладки

Примеры:

SELECT jsonb_build_object('ok', true, 'count', 3);

SELECT jsonb_pretty(payload) FROM events LIMIT 1;

SELECT key, value
FROM events, jsonb_each_text(payload) AS t(key, value)
WHERE event_id = 42;

Полный справочник операторов и функций — в документации PostgreSQL (официальный перевод Postgres Pro).


Конвертация JSON ↔ JSONB

-- текст json → jsonb (парсинг и нормализация)
SELECT '{"b": 2, "a": 1}'::jsonb;
-- порядок ключей в выводе может отличаться от исходника

-- jsonb → текст (для логов, экспорта)
SELECT payload::text FROM orders;

-- json → json (текст) без смены представления
SELECT col::json FROM t;

-- явное приведение между типами PostgreSQL
ALTER TABLE t ALTER COLUMN doc TYPE jsonb USING doc::jsonb;

Из приложения: сериализовать объект в строку JSON и передать с ::jsonb, либо использовать параметр с типом jsonb в драйвере.

Важно: при jsonb теряются insignificant whitespace и порядок ключей. Если нужно хранить байт-в-байт тело от партнёра для юридического аудита — иногда оставляют text или json, а для поиска дублируют нормализованный jsonb.


Индексы

Поиск по payload->>'field' без индекса даёт последовательное сканирование. Для JSONB в PostgreSQL основной инструмент — GIN:

CREATE INDEX idx_events_payload_gin
ON events USING GIN (payload);

CREATE INDEX idx_events_payload_path
ON events USING GIN (payload jsonb_path_ops);
Класс операторовСозданиеУдобно дляОграничение
jsonb_ops (по умолчанию)USING GIN (payload)@>, ?, `?, ?&`
jsonb_path_opsUSING GIN (payload jsonb_path_ops)@> по путям, компактнеенет ? / `?

Сравнения вида (payload->>'total')::numeric > 100 GIN по всему столбцу не ускоряет — нужен B-tree по выражению:

CREATE INDEX idx_orders_total ON orders (((payload->>'total')::numeric));

Теория GIN, GiST, частичных и составных индексов — в главе Сложные индексы. Создание индексов в DDL — DDL — определение структуры. Пошаговая проверка планов — Практикум PostgreSQL по JSONB.


Смежные материалы

ТемаСсылка
Текстовый JSON (API, конфиги)JSON
YAML, XMLYAML, XML
Бинарная сериализация вне БДMessagePack, BSON, Protobuf
PostgreSQL/encyclopedia/3-data-markup/3-07-sql/888
Основы SQL/encyclopedia/3-data-markup/3-07-sql/intro
SELECT, WHERE, JOIN/encyclopedia/3-data-markup/3-07-sql/885
ORM и работа с данными/encyclopedia/4-code-dev/4-10-orm-i-rabota-s-dannymi/intro
Интеграции и API/encyclopedia/2-system-network/2-09-osnovy-integratsionnogo-vzaimodeystviya/117

Дополнительно (вне энциклопедия):


Практическое задание
  1. Создайте таблицу с полем payload JSONB и вставьте документ с вложенным объектом и массивом (как в примерах выше).
  2. Выполните три фильтра: по ->>, по @> и по ?.
  3. Добавьте GIN-индекс и сравните EXPLAIN до и после — по шагам в практикуме JSONB.
  4. Сравните с тем же документом в типе json: сохраните с "красивыми" пробелами и убедитесь, что jsonb их не хранит.