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
| Параметр | json | jsonb |
|---|---|---|
| Формат хранения | Текстовая строка | Бинарное дерево |
| Скорость записи | Быстрее (без полного разбора) | Медленнее (парсинг и нормализация) |
| Скорость чтения по полям | Медленнее (разбор при каждом обращении) | Быстрее |
| Индексация | Практически не для поиска по ключам | 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_ops | USING 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, XML | YAML, 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 |
Дополнительно (вне энциклопедия):
- JSON и JSONB в PostgreSQL — Habr
- Типы json и jsonb — Postgres Pro
- Овладение типом JSONB — Logto
- JSON и JSONB — Arenadata
- Создайте таблицу с полем
payload JSONBи вставьте документ с вложенным объектом и массивом (как в примерах выше). - Выполните три фильтра: по
->>, по@>и по?. - Добавьте GIN-индекс и сравните
EXPLAINдо и после — по шагам в практикуме JSONB. - Сравните с тем же документом в типе
json: сохраните с "красивыми" пробелами и убедитесь, чтоjsonbих не хранит.