Что такое JSONB в PostgreSQL?
JSONB в PostgreSQL
PostgreSQL поддерживает два типа для хранения JSON-данных: json и jsonb. Оба принимают валидные JSON-значения, но отличаются способом хранения и возможностями.
Отличия JSON vs JSONB
| Характеристика | json | jsonb | |
|---|---|---|---|
| Хранение | Текст «как есть» | Бинарный разобранный формат | |
| Запись | Быстрее | Медленнее (разбор при вставке) | |
| Чтение/поиск | Медленнее (разбор при каждом чтении) | Быстрее | |
| Порядок ключей | Сохраняется | Не гарантируется | |
| Дублирующиеся ключи | Сохраняются все | Побеждает последний | |
| Индексы GIN/GiST | Нет | Да | |
| Поддержка операторов @>, <@, ?, ?&, ? | Нет | Да |
Когда использовать JSONB
JSONB предпочтителен в большинстве случаев, когда:
- нужен поиск по содержимому документа;
- данные читаются чаще, чем пишутся;
- требуется индексирование JSON-полей;
- важна производительность запросов.
Тип json оправдан лишь когда критично сохранить оригинальное форматирование или порядок ключей (например, для аудита).
Операторы JSONB
-- Проверка вхождения: содержит ли документ указанный фрагмент
SELECT * FROM users WHERE profile @> '{"role": "admin"}';
-- Проверка существования ключа
SELECT * FROM users WHERE profile ? 'email';
-- Проверка существования любого из ключей
SELECT * FROM users WHERE profile ?| ARRAY['email', 'phone'];
-- Проверка существования всех ключей
SELECT * FROM users WHERE profile ?& ARRAY['email', 'name'];
Индексирование JSONB
Главное преимущество JSONB — возможность создания GIN-индекса, который покрывает все ключи и значения документа:
-- Создание GIN-индекса по всему JSONB-полю
CREATE INDEX idx_users_profile ON users USING GIN (profile);
-- Индекс по конкретному пути (более компактный)
CREATE INDEX idx_users_role ON users USING BTREE ((profile->>'role'));
-- jsonb_path_ops — более компактный GIN-индекс, только для @>
CREATE INDEX idx_users_profile_path ON users USING GIN (profile jsonb_path_ops);
Работа с вложенными данными
-- Оператор -> возвращает JSONB
-- Оператор ->> возвращает текст
SELECT
profile -> 'address' AS address_json,
profile ->> 'name' AS name_text,
profile -> 'address' ->> 'city' AS city
FROM users;
-- Обновление отдельного ключа без замены всего документа
UPDATE users
SET profile = jsonb_set(profile, '{address, city}', '"Москва"')
WHERE id = 1;
-- Удаление ключа
UPDATE users
SET profile = profile - 'temp_field'
WHERE id = 1;
JSONPath (PostgreSQL 12+)
-- Поиск с использованием JSONPath
SELECT * FROM orders
WHERE jsonb_path_exists(data, '$.items[*] ? (@.price > 1000)');
Практические советы
- Не злоупотребляйте JSONB для данных с фиксированной структурой — реляционная схема обычно эффективнее.
- GIN-индекс ускоряет операторы
@>,?,?|,?&, но не операторы->и->>. - Для частых запросов по конкретному полю лучше создать выражённый B-tree индекс
((data->>'field')). - Функция
jsonb_pretty()удобна для отладки.
Что хочет услышать интервьюер
Понимание разницы между json и jsonb: бинарное хранение, скорость чтения vs записи, порядок ключей
Знание специфических операторов JSONB: @>, ?, ?|, ?&, ->, ->>
Понимание индексирования: GIN-индекс и его роль в производительности запросов
Умение обновлять отдельные поля через jsonb_set без перезаписи всего документа
Осознание границ применимости: когда JSONB оправдан, а когда лучше нормализованная схема
Пример: Создание таблицы с JSONB и базовые операции
-- Создание таблицы с JSONB-полем
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
meta JSONB
);
-- Вставка данных
INSERT INTO products (name, meta) VALUES
('Ноутбук', '{"brand": "Apple", "specs": {"ram": 16, "ssd": 512}, "tags": ["pro", "m3"]}'),
('Мышь', '{"brand": "Logitech", "specs": {"dpi": 4000}, "tags": ["wireless"]}');
-- Получить значение поля как текст
SELECT name, meta->>'brand' AS brand FROM products;
-- Найти все товары бренда Apple
SELECT * FROM products WHERE meta @> '{"brand": "Apple"}';
-- Найти товары с тегом 'wireless'
SELECT * FROM products WHERE meta @> '{"tags": ["wireless"]}';
-- GIN-индекс для ускорения поиска
CREATE INDEX idx_products_meta ON products USING GIN (meta);
-- Обновить только одно поле внутри JSONB
UPDATE products
SET meta = jsonb_set(meta, '{specs, ram}', '32')
WHERE id = 1;
-- Удалить ключ из JSONB
UPDATE products
SET meta = meta - 'tags'
WHERE id = 2;
Типичные ошибки
Путают операторы -> (возвращает JSONB) и ->> (возвращает text), что приводит к ошибкам сравнения типов
Забывают создать GIN-индекс и удивляются медленной работе оператора @> на больших таблицах
Используют JSONB там, где данные имеют стабильную структуру — теряют преимущества реляционной модели
Полностью перезаписывают весь JSON при обновлении одного поля вместо использования jsonb_set
Не учитывают, что JSONB не сохраняет порядок ключей и удаляет дублирующиеся ключи


