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

Приведи пример самых вероятных оптимизаций в PostgreSQL

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

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

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

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

Приведи пример самых вероятных оптимизаций в PostgreSQL

Оптимизация БД — это часто первое, что нужно делать перед скалированием приложения. Вот реальные примеры.

1. Добавление индексов (Index)

Это самая частая оптимизация. Индекс ускоряет поиск данных в таблице.

Проблема: N+1 queries и медленные поиски

# ❌ Медленно: полная проверка всей таблицы
SELECT * FROM users WHERE email = 'john@example.com';  # 1-10 сек на 1M rows

# ✅ Быстро: индекс позволяет найти за ms
CREATE INDEX idx_users_email ON users(email);
SELECT * FROM users WHERE email = 'john@example.com';  # 1-5 ms

В Python коде:

from django.db import models

class User(models.Model):
    email = models.EmailField(unique=True)  # уникальный индекс
    created_at = models.DateTimeField()
    
    class Meta:
        indexes = [
            models.Index(fields=['email']),
            models.Index(fields=['created_at', 'email']),
        ]

# Migration:
# python manage.py makemigrations
# python manage.py migrate

Типичные индексы:

-- Простой индекс
CREATE INDEX idx_users_created_at ON users(created_at);

-- Composite индекс (для WHERE и ORDER BY вместе)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);

-- Partial индекс (для активных пользователей)
CREATE INDEX idx_active_users ON users(id) WHERE is_active = true;

-- BRIN индекс (для больших таблиц, сортированных данных)
CREATE INDEX idx_logs_timestamp ON logs USING BRIN (created_at);

2. SELECT правильные колонки (Column Selection)

Проблема: Загружаем лишние данные

# ❌ Загружаем всё
users = User.objects.all()  # SELECT * FROM users
for user in users:
    print(user.email)  # Но нужен только email!

# ✅ Загружаем только нужное
users = User.objects.values_list('email', flat=True)
for email in users:
    print(email)  # Намного быстрее!

В SQL:

-- ❌ Неправильно: 1000 колонок × 1M rows = огромный объём данных
SELECT * FROM users;

-- ✅ Правильно: 2 колонки
SELECT id, email FROM users;

3. JOIN вместо N+1 queries

Это огромная проблема в Django и FastAPI!

Проблема: N+1 queries

# ❌ 1001 запрос! (1 для users + 1000 для profiles)
users = User.objects.all()  # 1 запрос
for user in users:           # 1000 пользователей
    print(user.profile.bio)  # 1000 запросов!

# ✅ 1 запрос с JOIN
users = User.objects.select_related('profile')  # 1 запрос с JOIN
for user in users:
    print(user.profile.bio)  # Уже в памяти!

В SQL:

-- ❌ Медленно (выполняется 1001 раз)
SELECT * FROM users;
FOR EACH user:
    SELECT bio FROM profiles WHERE user_id = {user_id};

-- ✅ Быстро (1 запрос)
SELECT u.*, p.bio FROM users u
INNER JOIN profiles p ON u.id = p.user_id;

Варианты JOIN:

# select_related для One-to-One и Foreign Key (INNER JOIN)
users = User.objects.select_related('profile')

# prefetch_related для Many-to-Many и Reverse FK (отдельные запросы)
posts = Post.objects.prefetch_related('comments', 'tags')

# Комбинированно
users = User.objects.select_related('profile').prefetch_related('posts')

4. WHERE условия для фильтрации (Filtering)

Проблема: Загружаем всё, затем фильтруем в Python

# ❌ Загружаем 1M пользователей, фильтруем в Python
all_users = User.objects.all()  # 1M rows
active_users = [u for u in all_users if u.is_active]

# ✅ Фильтруем на БД
active_users = User.objects.filter(is_active=True)  # ~100k rows

Оптимизация WHERE:

# ❌ Медленно: нет индекса, нужно сканировать всю таблицу
User.objects.filter(username__startswith='john')  # LIKE 'john%'

# ✅ Быстро: с индексом
User.objects.filter(email=exact_email)  # = с индексом
User.objects.filter(created_at__gte=date)  # >= с индексом
User.objects.filter(is_active=True)  # = с индексом

5. GROUP BY и Aggregation

Проблема: Группируем в Python вместо БД

# ❌ Медленно
orders = Order.objects.all()
revenue_by_user = {}
for order in orders:
    user_id = order.user_id
    if user_id not in revenue_by_user:
        revenue_by_user[user_id] = 0
    revenue_by_user[user_id] += order.amount

# ✅ Быстро: GROUP BY на БД
from django.db.models import Sum

revenue = Order.objects.values('user_id').annotate(
    total=Sum('amount')
).order_by('user_id')

В SQL:

-- ✅ GROUP BY на БД — максимум эффективно
SELECT user_id, SUM(amount) as total
FROM orders
GROUP BY user_id
ORDER BY user_id;

6. LIMIT для больших результатов

Проблема: Загружаем всё вместо pagination

# ❌ Загружаем все 10M записей в память
all_posts = Post.objects.all()

# ✅ Берём только нужную страницу
page_size = 20
page = 1
posts = Post.objects.all()[page * page_size:(page + 1) * page_size]

В SQL:

-- LIMIT + OFFSET для пагинации
SELECT * FROM posts
LIMIT 20 OFFSET 0;  -- Page 1
LIMIT 20 OFFSET 20; -- Page 2
LIMIT 20 OFFSET 40; -- Page 3

7. Денормализация и Query Optimization

Проблема: Сложные JOIN'ы и GROUP BY

-- Очень сложный запрос (медленный)
SELECT u.username, COUNT(p.id) as post_count,
       COUNT(c.id) as comment_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
LEFT JOIN comments c ON p.id = c.post_id
GROUP BY u.id;

Решение: Денормализация (добавляем redundant колонки)

# Добавляем счётчики в таблицу users
class User(models.Model):
    username = models.CharField(max_length=255)
    post_count = models.IntegerField(default=0)  # Денормализованное значение
    comment_count = models.IntegerField(default=0)
    
    def update_counts(self):
        self.post_count = self.posts.count()
        self.comment_count = self.comments.count()
        self.save()

# Теперь запрос простой и быстрый
users = User.objects.all().values('username', 'post_count', 'comment_count')

8. ANALYZE и Query Plan

Как найти медленные запросы:

-- Включаем логирование медленных запросов
ALTER SYSTEM SET log_min_duration_statement = 1000;  -- 1 сек
SELECT pg_reload_conf();

-- Смотрим план запроса
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123;

-- Результат покажет:
-- Seq Scan — полная проверка (медленно)
-- Index Scan — индекс поиск (быстро)

В Python для мониторинга:

from django.db import connection
from django.test.utils import override_settings

@override_settings(DEBUG=True)
def get_expensive_data():
    users = User.objects.all()
    # ...
    print(connection.queries)  # Все запросы
    for query in connection.queries:
        print(query['sql'], query['time'])

9. Connection Pool для многих подключений

Проблема: Слишком много коннекций к БД

# ❌ Каждый запрос — новое подключение (100ms overhead)
for i in range(1000):
    user = User.objects.get(id=i)  # Новое подключение

# ✅ Connection Pool переиспользует подключения
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'CONN_MAX_AGE': 600,  # Переиспользуем на 600 сек
        'ATOMIC_REQUESTS': True,
    }
}

# Или pgBouncer
# pgBouncer pooler для PostgreSQL

10. VACUUM и автоклин

Проблема: Мёртвые строки накапливаются

-- PostgreSQL автоматически удаляет мёртвые строки
ALTER SYSTEM SET autovacuum = on;
ALTER SYSTEM SET autovacuum_naptime = '1 min';

-- Или ручной VACUUM
VACUUM ANALYZE users;  -- Оптимизирует и обновляет статистику

Чеклист оптимизации

  1. Профилирование — найди медленные запросы

    django-silk  # Django профайлер
    
  2. Добавь индексы

    models.Index(fields=['email'])
    
  3. Исправь N+1 queries

    .select_related('profile').prefetch_related('comments')
    
  4. Правильная фильтрация

    .filter(is_active=True, created_at__gte=date)
    
  5. SELECT нужные колонки

    .values('id', 'email').only('id', 'email')
    
  6. LIMIT для больших результатов

    [0:20]  # Pagination
    
  7. GROUP BY и Aggregation на БД

    .annotate(Sum('amount')).values('user_id')
    
  8. ANALYZE план запроса

    EXPLAIN ANALYZE SELECT ...;
    

Реальный пример оптимизации

Было: 15 сек для получения пользователей с статистикой

# ❌ Исходный медленный код
users = User.objects.all()
for user in users:
    post_count = user.posts.count()  # N queries
    print(f"{user.email}: {post_count} posts")

Стало: 100ms

# ✅ Оптимизированный код
from django.db.models import Count

users = User.objects.annotate(
    post_count=Count('posts')  # 1 JOIN запрос
).values('email', 'post_count')

for user in users:
    print(f"{user['email']}: {user['post_count']} posts")

Вывод

Топ-5 оптимизаций PostgreSQL:

  1. Индексы на WHERE колонки (+95% improvement)
  2. select_related/prefetch_related (убирает N+1)
  3. Правильная фильтрация на БД (не в Python)
  4. GROUP BY вместо Python (масштабируется)
  5. Pagination с LIMIT (не загружай всё)

Обычно эти оптимизации дают 10-100х ускорение без изменения архитектуры.

Приведи пример самых вероятных оптимизаций в PostgreSQL | PrepBro