← Назад к вопросам
Как профилируешь запросы в базе 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 - ✅ Кешируй результаты запросов
- ✅ Избегай циклов с запросами
- ✅ Логируй все запросы в разработке
- ✅ Проверяй количество запросов в тестах
- ✅ Добавляй индексы на часто-фильтруемые поля