Введение
Большинство примеров предыдущих блоков цикла использовали синтаксис PostgreSQL как ориентир — в этой статье разберём, что делает PostgreSQL особенным среди реляционных СУБД: расширяемость через систему плагинов, и нативную поддержку JSONB, дающую гибридный подход между строгой реляционной моделью и документ-ориентированной гибкостью.
Концепция
PostgreSQL поддерживает богатую систему расширений (extensions) — от уже упомянутых GIN/GiST индексов (статья 418) до специализированных модулей для геоданных (PostGIS), временных рядов (TimescaleDB), и многого другого, устанавливаемых отдельно от базовой установки по мере необходимости. JSONB — бинарный, индексируемый (в отличие от просто текстового JSON) формат хранения JSON-документов прямо в столбце реляционной таблицы, что позволяет сочетать строгую структуру обычных столбцов с гибкой, переменной структурой отдельных полей, не вписывающихся естественно в фиксированную реляционную схему.
Пример кода: PostgreSQL
-- JSONB столбец — гибкая структура рядом с обычными, строго типизированными столбцами
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
attributes JSONB -- разные товары могут иметь совершенно разный набор атрибутов
);
INSERT INTO products (name, attributes) VALUES
('Ноутбук', '{"cpu": "i7", "ram_gb": 16, "ssd_gb": 512}'),
('Книга', '{"author": "Иванов И.И.", "pages": 320, "isbn": "978-5-..."}');
-- Запросы к данным внутри JSONB — извлечение полей, фильтрация по значению внутри документа
SELECT name, attributes->>'cpu' AS cpu FROM products WHERE attributes->>'cpu' IS NOT NULL;
SELECT name FROM products WHERE attributes->'ram_gb' > '8'::jsonb; -- сравнение с числовым значением
-- Индекс по JSONB-столбцу (GIN, статья 418) — эффективный поиск по содержимому документа
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);
SELECT name FROM products WHERE attributes @> '{"cpu": "i7"}'::jsonb; -- "содержит этот фрагмент"
-- Расширения — установка и использование (пример: pg_trgm для нечёткого текстового поиска)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_customers_name_trgm ON customers USING GIN (name gin_trgm_ops);
SELECT name FROM customers WHERE name % 'Иван Петров'; -- поиск по схожести (с учётом опечаток)
Пояснения к коду
attributes->>'cpu' (оператор ->>, возвращающий значение поля как текст) и attributes->'ram_gb' (оператор ->, возвращающий значение как JSONB, что позволяет дальнейшие операции с типом, как сравнение > '8'::jsonb) показывают два родственных, но различных оператора доступа к полям JSONB — выбор между ними зависит от того, нужно ли получить значение как обычный текст для дальнейшей обработки, или как JSONB-значение для сохранения типа и возможности дальнейших JSONB-специфичных операций. attributes @> '{"cpu": "i7"}'::jsonb (оператор «содержит») показывает практичный способ фильтрации по конкретному фрагменту JSON-документа, эффективно использующий GIN-индекс. CREATE EXTENSION pg_trgm показывает механизм расширений на практике — pg_trgm добавляет поддержку нечёткого, основанного на схожести текстового поиска (через триграммы — последовательности из трёх символов), не входящую в базовую функциональность PostgreSQL, но доступную одной командой установки расширения.
Подводные камни
- Избыточное использование JSONB вместо обычных, строго типизированных столбцов для данных, структура которых на самом деле известна и стабильна заранее — JSONB даёт гибкость, но теряет некоторые преимущества обычных столбцов (более строгая проверка типов на уровне схемы, более простые и предсказуемые индексы для большинства обычных операций, обычно более эффективное хранение для регулярной, предсказуемой структуры); JSONB оправдан прежде всего для данных с реально переменной, не предсказуемой заранее структурой (как в примере с разными атрибутами разных категорий товаров), а не как универсальная замена обычным столбцам «на всякий случай».
- Отсутствие индекса для JSONB-столбца, по которому выполняется частая фильтрация, что превращает каждый такой запрос в полный просмотр и разбор JSON-содержимого каждой строки — аналогично обычным столбцам (статья 413), JSONB-поля, активно используемые в условиях
WHERE, нуждаются в соответствующем (обычно GIN) индексе для эффективного выполнения, и его отсутствие легко упускается из виду именно из-за «гибкой», не явно структурированной природы JSONB-данных. - Установка расширений в production без предварительного тестирования на менее критичном окружении — хотя расширения PostgreSQL обычно стабильны и широко используются, установка нового, ранее не использованного расширения непосредственно на production-системе без предварительной проверки на тестовом окружении несёт некоторый риск неожиданного взаимодействия с существующей конфигурацией или нагрузкой системы, и стандартная практика тестирования изменений перед production (общая для всего цикла статей) остаётся в силе и для добавления расширений.
- Чрезмерная зависимость от специфичных для PostgreSQL возможностей (JSONB, конкретные расширения) в проекте, для которого может возникнуть необходимость переноса на другую СУБД в будущем — аналогично подводному камню статьи 418 про специализированные индексы, использование специфичных возможностей PostgreSQL даёт реальные практические выгоды, но создаёт привязку к конкретной СУБД, которую стоит явно осознавать как архитектурный выбор, особенно если требование к переносимости между разными СУБД заявлено как значимое для конкретного проекта.