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

Как профилируешь запросы в базе Django?

2.0 Middle🔥 161 комментариев
#DevOps и инфраструктура#Django

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

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

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

Профилирование запросов в БД Django

Есть несколько инструментов, от встроенных до профессиональных.

1. Django Debug Toolbar (проще всего)

Устанавливаем:

pip install django-debug-toolbar

Конфигурируем:

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'debug_toolbar',
]

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

INTERNAL_IPS = ['127.0.0.1']
# urls.py
if settings.DEBUG:
    import debug_toolbar
    urlpatterns += [
        path('__debug__/', include(debug_toolbar.urls)),
    ]

Результат: панель в браузере с всеми запросами к БД.

2. django.db.connection.queries (в тестах)

from django.db import connection
from django.test import TestCase

class UserTestCase(TestCase):
    def test_user_retrieval(self):
        # Выполнить код
        user = User.objects.get(id=1)
        
        # Проверить запросы
        print(f"Всего запросов: {len(connection.queries)}")
        
        for query in connection.queries:
            print(f"SQL: {query['sql']}")
            print(f"Время: {query['time']}s")

Практический пример:

from django.test import TestCase
from django.test.utils import override_settings
from django.db import connection
from django.test import TestCase as DjangoTestCase

class ProfileQueryTestCase(DjangoTestCase):
    def test_n_plus_one_problem(self):
        # Создать данные
        authors = [Author.objects.create(name=f"Author {i}") for i in range(5)]
        for author in authors:
            [Book.objects.create(author=author, title=f"Book {i}") for i in range(10)]
        
        # Плохой запрос (N+1 проблема)
        connection.queries_log.clear()
        books = Book.objects.all()
        for book in books:
            print(book.author.name)  # +1 запрос на каждую книгу!
        
        print(f"Запросов: {len(connection.queries)}")  # ~51 запрос!
        
        # Хороший запрос
        connection.queries_log.clear()
        books = Book.objects.select_related('author').all()
        for book in books:
            print(book.author.name)  # Всё в памяти
        
        print(f"Запросов: {len(connection.queries)}")  # 1 запрос

3. assertNumQueries (в тестах)

from django.test import TestCase
from django.test.utils import assertNumQueries

class OptimizationTestCase(TestCase):
    def test_user_list_query_count(self):
        # Создать пользователей
        [User.objects.create(name=f"User {i}") for i in range(10)]
        
        # Проверить количество запросов
        with self.assertNumQueries(1):
            users = User.objects.all()
            list(users)  # Вынуждаем выполнение запроса

4. Django Extensions (shell_plus с профилированием)

pip install django-extensions
# settings.py
INSTALLED_APPS = [
    'django_extensions',
]
python manage.py shell_plus

5. Database Profiler middleware

# middleware.py
import logging
from django.db import connection

logger = logging.getLogger(__name__)

class DatabaseProfilerMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        connection.queries_log.clear()
        response = self.get_response(request)
        
        # Статистика
        total_time = sum(float(q['time']) for q in connection.queries)
        logger.info(f"Total DB queries: {len(connection.queries)}, Time: {total_time:.2f}s")
        
        # Самые долгие запросы
        slow_queries = sorted(
            connection.queries,
            key=lambda x: float(x['time']),
            reverse=True
        )[:3]
        
        for query in slow_queries:
            logger.warning(f"Slow query ({query['time']}s): {query['sql'][:100]}")
        
        return response

6. SQLparse для красивого вывода

pip install sqlparse
from django.db import connection
import sqlparse

# Выполнить операцию
User.objects.filter(is_active=True).count()

# Вывести красиво
for query in connection.queries:
    sql = query['sql']
    formatted_sql = sqlparse.format(sql, reindent=True, keyword_case='upper')
    print(formatted_sql)
    print(f"Time: {query['time']}s\n")

7. Логирование всех запросов

# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG',
        },
    },
}
python manage.py runserver --verbosity 2

8. Контекстный менеджер для профилирования

from django.db import connection
from contextlib import contextmanager
import time

@contextmanager
def query_debugger(description="Operation"):
    queries_before = len(connection.queries)
    time_start = time.time()
    
    yield
    
    queries_after = len(connection.queries)
    elapsed = time.time() - time_start
    
    print(f"\n{description}")
    print(f"Queries: {queries_after - queries_before}")
    print(f"Time: {elapsed:.4f}s")
    
    for query in connection.queries[queries_before:queries_after]:
        print(f"  - {query['sql'][:80]}")

# Использование
with query_debugger("Fetch active users"):
    users = User.objects.filter(is_active=True)
    list(users)

9. Optimize QuerySets (практические примеры)

Плохо (N+1):

users = User.objects.all()
for user in users:
    print(user.profile.bio)  # +1 запрос на пользователя

Хорошо:

users = User.objects.select_related('profile')
for user in users:
    print(user.profile.bio)  # Уже в памяти

Для Many-to-Many:

# Плохо
books = Book.objects.all()
for book in books:
    authors = list(book.authors.all())  # +1 запрос

# Хорошо
books = Book.objects.prefetch_related('authors')
for book in books:
    authors = list(book.authors.all())  # В памяти

10. Команда для анализа migrations

python manage.py sqlmigrate app_name 0001

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

  • ✅ Используй select_related() для ForeignKey
  • ✅ Используй prefetch_related() для ManyToMany
  • ✅ Кешируй результаты запросов
  • ✅ Избегай циклов с запросами
  • ✅ Логируй все запросы в разработке
  • ✅ Проверяй количество запросов в тестах
  • ✅ Добавляй индексы на часто-фильтруемые поля