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

Как определить, что запрос к базе данных выполняется медленно?

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

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

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

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

Определение медленных запросов к БД

Это критический навык для любого backend разработчика. Медленные запросы — часто главная причина проблем с перформансом.

Метод 1: Логирование времени выполнения (самый простой)

import time
from contextlib import contextmanager

@contextmanager
def measure_query_time(query_name: str, slow_threshold: float = 0.1):
    start = time.time()
    try:
        yield
    finally:
        duration = time.time() - start
        if duration > slow_threshold:
            print(f"SLOW QUERY: {query_name} took {duration:.3f}s")
        else:
            print(f"OK: {query_name} took {duration:.3f}s")

with measure_query_time("get_users", slow_threshold=0.05):
    users = User.objects.all()

Метод 2: Django Debug Toolbar (для разработки)

# settings.py
INSTALLED_APPS = [
    'debug_toolbar',
]

MIDDLEWARE = [
    'debug_toolbar.middleware.DebugToolbarMiddleware',
]

INTERNAL_IPS = ['127.0.0.1']

После этого в браузере видно все запросы, их время и SQL.

Метод 3: Анализ SQL запросов PostgreSQL

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

@override_settings(DEBUG=True)
def analyze_queries():
    from django.db import connection, reset_queries
    from django.conf import settings
    
    reset_queries()
    settings.DEBUG = True
    
    users = User.objects.filter(is_active=True)
    
    for query in connection.queries:
        print(f"Time: {query['time']}s")
        print(f"SQL: {query['sql']}")

def slow_query_logger():
    from django.db import connection
    from django.conf import settings
    
    if settings.DEBUG:
        for query in connection.queries:
            time = float(query['time'])
            if time > 0.1:
                print(f"SLOW: {query['sql']}")
                print(f"Time: {time}s")

Метод 4: PostgreSQL EXPLAIN (самый точный)

def analyze_query_plan(sql: str):
    import psycopg2
    
    conn = psycopg2.connect(
        dbname='mydb',
        user='user',
        password='password',
        host='localhost'
    )
    
    cursor = conn.cursor()
    cursor.execute(f"EXPLAIN ANALYZE {sql}")
    
    plan = cursor.fetchall()
    for line in plan:
        print(line[0])
    
    cursor.close()
    conn.close()

Метод 5: Django ORM и проблемные паттерны

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

# Плохо
users = User.objects.all()
for user in users:
    print(user.profile.bio)  # Для каждого user запрос!

# Хорошо
users = User.objects.select_related('profile').all()
for user in users:
    print(user.profile.bio)  # Уже загружено

# Для Many-to-Many
users = User.objects.prefetch_related('groups').all()
for user in users:
    print(list(user.groups.all()))  # Уже загружено

Проблема 2: Фильтрация после загрузки

# Плохо
active_users = [u for u in User.objects.all() if u.is_active]

# Хорошо
active_users = User.objects.filter(is_active=True)

Проблема 3: COUNT на большой таблице

# Плохо
count = User.objects.count()  # Полное сканирование

# Хорошо
count = User.objects.filter(is_active=True).count()  # С индексом быстро

# Очень большие таблицы
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
    SELECT reltuples::bigint AS estimate 
    FROM pg_class 
    WHERE relname = 'auth_user'
""")
print(cursor.fetchone()[0])

Метод 6: Мониторинг в production с логированием

import logging
import time
from functools import wraps

logger = logging.getLogger('queries')

def log_slow_queries(threshold: float = 0.1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            duration = time.time() - start
            
            if duration > threshold:
                logger.warning(
                    f"Slow query in {func.__name__}: {duration:.3f}s",
                    extra={
                        'function': func.__name__,
                        'duration': duration,
                    }
                )
            else:
                logger.debug(f"{func.__name__}: {duration:.3f}s")
            
            return result
        return wrapper
    return decorator

@log_slow_queries(threshold=0.05)
def get_user_with_orders(user_id: int):
    return User.objects.select_related('profile').prefetch_related(
        'orders'
    ).get(id=user_id)

Метод 7: PostgreSQL встроенные инструменты

-- Найти самые медленные запросы
SELECT 
    query,
    calls,
    total_time,
    mean_time,
    max_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;

-- Найти запросы с Sequential Scan
EXPLAIN ANALYZE SELECT * FROM users WHERE name LIKE '%john%';

-- Создать индекс
CREATE INDEX idx_users_name ON users(name);

Практический пример: Django с мониторингом

import logging
from functools import wraps
import time

logger = logging.getLogger('performance')

def track_query_performance(view_func):
    @wraps(view_func)
    def wrapper(request, *args, **kwargs):
        from django.db import connection, reset_queries
        from django.conf import settings
        
        reset_queries()
        settings.DEBUG = True
        
        start = time.time()
        response = view_func(request, *args, **kwargs)
        duration = time.time() - start
        
        total_queries = len(connection.queries)
        total_time = sum(float(q['time']) for q in connection.queries)
        
        logger.info(
            f"{view_func.__name__}: {total_queries} queries in {total_time:.3f}s (view: {duration:.3f}s)"
        )
        
        for query in connection.queries:
            query_time = float(query['time'])
            if query_time > 0.1:
                logger.warning(f"Slow: {query['sql'][:100]} ({query_time:.3f}s)")
        
        return response
    
    return wrapper

@track_query_performance
def user_orders_view(request, user_id):
    # Правильно — prefetch
    user = User.objects.prefetch_related('orders').get(id=user_id)
    orders = user.orders.all()
    
    return JsonResponse({
        'user': user.username,
        'orders_count': len(orders),
    })

Инструменты для мониторинга

  • New Relic: APM мониторинг
  • Datadog: анализ медленных запросов
  • DjangoQueryCount: подсчёт запросов
  • django-silk: профилирование в development
  • pgAdmin: анализ PostgreSQL

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

  • Используй select_related() для ForeignKey
  • Используй prefetch_related() для Many-to-Many
  • Филтруй в БД, не в памяти
  • Создай индексы для часто используемых полей
  • Избегай COUNT на огромных таблицах
  • Логируй медленные запросы
  • Регулярно анализируй EXPLAIN ANALYZE
  • Кешируй результаты где возможно
Как определить, что запрос к базе данных выполняется медленно? | PrepBro