← Назад к вопросам

Когда нужно использовать jsonb тип данных в PostgreSQL?

2.0 Middle🔥 131 комментариев
#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование JSONB типа данных в PostgreSQL

JSONB — это тип данных в PostgreSQL, который хранит JSON в бинарном формате, обеспечивая быстрый поиск, индексирование и запросы. Это мощный инструмент, но его нужно использовать обдуманно.

1. Что такое JSONB и отличие от JSON

-- JSON: хранится как текст, медленнее
CREATE TABLE users_json AS (
    id SERIAL PRIMARY KEY,
    data JSON
);

-- JSONB: хранится в бинарном формате, быстрее
CREATE TABLE users_jsonb AS (
    id SERIAL PRIMARY KEY,
    data JSONB
);

-- JSONB компактнее на диске и быстрее в запросах
-- Недостаток JSONB: медленнее при записи (нужно парсить)

2. Когда использовать JSONB

Используйте JSONB когда:

a) Данные часто меняют структуру

# Без JSONB: нужна миграция при каждом изменении
class User(models.Model):
    name = models.CharField()
    email = models.CharField()
    # Если нужно добавить profile_picture, нужна миграция!

# С JSONB: гибкость
class User(models.Model):
    name = models.CharField()
    email = models.CharField()
    metadata = models.JSONField()  # Может содержать любые поля

# Python
user = User.objects.create(
    name="John",
    email="john@example.com",
    metadata={
        "profile_picture": "url",
        "bio": "Something",
        "preferences": {
            "notifications": True,
            "theme": "dark"
        }
    }
)

b) Данные содержат вложенные структуры

-- JSONB идеален для иерархических данных
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR,
    specs JSONB
);

-- Вставка
INSERT INTO products (name, specs) VALUES (
    'Laptop',
    '{
        "processor": {
            "brand": "Intel",
            "cores": 8,
            "frequency": "3.2 GHz"
        },
        "memory": {
            "type": "DDR4",
            "size_gb": 16
        },
        "storage": {
            "type": "SSD",
            "size_gb": 512
        }
    }'::jsonb
);

-- Запрос вложенных данных
SELECT * FROM products 
WHERE specs -> 'processor' ->> 'brand' = 'Intel';

c) Нужны быстрые поиски по JSON данным

-- GIN индекс позволяет быстро искать в JSONB
CREATE INDEX idx_user_metadata 
ON users USING GIN (metadata);

-- Теперь этот запрос выполнится быстро
SELECT * FROM users 
WHERE metadata @> '{"role": "admin"}';

d) Опциональные поля, которые есть не у всех объектов

# Вместо множества nullable полей
class UserProfile(models.Model):
    user = models.ForeignKey(User)
    phone = models.CharField(null=True, blank=True)
    address = models.CharField(null=True, blank=True)
    company = models.CharField(null=True, blank=True)
    # Это плохо масштабируется!

# Используйте JSONB
class UserProfile(models.Model):
    user = models.ForeignKey(User)
    data = models.JSONField()

# Python
profile = UserProfile.objects.create(
    user=user,
    data={
        "phone": "123-456",
        "company": "Acme Corp"
        # address не нужен — просто не включим
    }
)

e) Быстрое добавление новых полей без миграции

# Динамическое расширение данных
user = User.objects.get(id=1)
user.metadata['last_login'] = "2024-01-15"
user.metadata['login_count'] = user.metadata.get('login_count', 0) + 1
user.save()

3. Когда НЕ использовать JSONB

Не используйте JSONB когда:

a) Данные структурированы и не меняются

# Плохо: использование JSONB для структурированных данных
class Blog(models.Model):
    title = models.CharField()
    content = models.TextField()
    author_metadata = models.JSONField()  # {"name": "John", "email": "john@example.com"}

# Хорошо: отдельная модель
class Author(models.Model):
    name = models.CharField()
    email = models.EmailField()

class Blog(models.Model):
    title = models.CharField()
    content = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

b) Нужны сложные JOIN'ы и связи между данными

# Плохо: JSONB для данных, которые должны быть нормализованы
class Order(models.Model):
    order_date = models.DateField()
    items = models.JSONField()  # [{"product_id": 1, "qty": 2}]

# Хорошо: отдельная таблица
class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField()

c) Нужна высокая производительность при записи

JSONB медленнее при вставке/обновлении, так как нужно парсить и валидировать JSON.

d) Данные очень большие (> 100 MB)

JSONB может увеличить потребление памяти и замедлить работу.

4. Примеры использования в Django + Python

Сохранение настроек пользователя

from django.db import models

class UserSettings(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    settings = models.JSONField(default=dict)

# Создание
settings = UserSettings.objects.create(
    user=user,
    settings={
        "theme": "dark",
        "language": "en",
        "notifications": {
            "email": True,
            "push": False,
            "sms": False
        }
    }
)

# Обновление вложенного поля
settings.settings['theme'] = 'light'
settings.save()

# Запрос по вложенному полю
dark_users = UserSettings.objects.filter(
    settings__theme='dark'
)

Логирование с дополнительными данными

from django.db import models
import json

class AuditLog(models.Model):
    action = models.CharField(max_length=100)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_now_add=True)
    details = models.JSONField()

# Использование
AuditLog.objects.create(
    action='user_updated',
    user=user,
    details={
        'changed_fields': ['email', 'name'],
        'old_values': {'email': 'old@example.com'},
        'new_values': {'email': 'new@example.com'},
        'ip_address': '192.168.1.1',
        'user_agent': 'Mozilla/5.0...'
    }
)

Теги и гибкие метаданные

class Article(models.Model):
    title = models.CharField()
    content = models.TextField()
    tags = models.JSONField()  # Вместо отдельной таблицы M2M
    metadata = models.JSONField(default=dict)

# Использование
article = Article.objects.create(
    title="Python Оптимизация",
    content="...",
    tags=["python", "optimization", "performance"],
    metadata={
        "seo": {
            "description": "...",
            "keywords": "python, optimization"
        },
        "social": {
            "twitter": True,
            "facebook": False
        }
    }
)

# Запрос
Article.objects.filter(tags__contains=['python'])

5. Запросы к JSONB

-- Оператор @> (contains)
SELECT * FROM users 
WHERE metadata @> '{"role": "admin"}';

-- Оператор <@ (contained by)
SELECT * FROM users 
WHERE '{"role": "admin"}' <@ metadata;

-- Оператор ? (has key)
SELECT * FROM users 
WHERE metadata ? 'last_login';

-- Оператор ->> (get text value)
SELECT metadata ->> 'role' FROM users;

-- Оператор -> (get JSON value)
SELECT metadata -> 'preferences' FROM users;

-- Вложенный доступ
SELECT metadata -> 'preferences' ->> 'theme' 
FROM users 
WHERE metadata -> 'preferences' ->> 'theme' = 'dark';

-- jsonb_array_elements для распаковки массивов
SELECT user_id, jsonb_array_elements(tags) AS tag
FROM articles;

6. Производительность

-- Без индекса: медленно
SELECT * FROM users WHERE metadata ->> 'role' = 'admin';

-- С GIN индексом: быстро
CREATE INDEX idx_user_role ON users USING GIN (metadata);

-- Еще лучше для specific keys
CREATE INDEX idx_user_role_specific 
ON users USING GIN ((metadata -> 'role'));

-- Проверка, что индекс используется
EXPLAIN ANALYZE
SELECT * FROM users WHERE metadata ->> 'role' = 'admin';

Итоговые рекомендации

Используйте JSONB для:

  • Гибких и часто меняющихся данных
  • Опциональных полей, которые есть не у всех объектов
  • Вложенных структур данных
  • Быстрого добавления новых полей без миграции

Не используйте JSONB для:

  • Жестко структурированных данных (используйте нормальные поля)
  • Данных с частыми JOIN'ами (нормализуйте)
  • Критичных по производительности систем с большим объемом записей
  • Очень больших данных (> 100 MB)

Правило: Если вы не уверены, использовать ли JSONB — скорее всего, не используйте. Начните с нормализованной схемы и добавьте JSONB только если явно нужна гибкость.