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

Какие методы QuerySet отправляют запросы к БД?

1.7 Middle🔥 251 комментариев
#Django

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

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

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

Методы QuerySet в Django, которые отправляют запросы к БД

QuerySet в Django использует ленивое вычисление (lazy evaluation). Запрос к БД отправляется только когда вы явно "оцениваете" QuerySet. Знание этого критично для оптимизации производительности.

1. Основной принцип: Ленивое вычисление

QuerySet не отправляет запрос сразу, а создает объект, готовый к отправке:

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

# Нет запроса
qs = User.objects.filter(age__gte=18)  # SQL еще не выполнен!
print(len(connection.queries))  # 0 запросов

# Запрос отправляется только при оценке
users_list = list(qs)  # SQL ВЫПОЛНЕН
print(len(connection.queries))  # 1 запрос
print(connection.queries[-1]['sql'])  # Видим выполненный SQL

2. Методы, которые ОТПРАВЛЯЮТ запрос (Evaluating QuerySet)

A. Явное преобразование:

# list() — самый распространенный
users = list(User.objects.all())  # Запрос отправляется

# tuple()
users_tuple = tuple(User.objects.filter(age=30))  # Запрос отправляется

# str() / repr()
print(str(User.objects.all()))  # Запрос НЕ отправляется (показывает SQL)
print(repr(User.objects.all()))  # Запрос НЕ отправляется

# bool() — проверка существования
if User.objects.filter(username='john').exists():  # Отправляется оптимизированный запрос
    print("Пользователь существует")

if User.objects.filter(username='john'):  # Проверка на true/false отправляет весь QuerySet
    print("Пользователь существует")

B. Итерирование:

# for loop
for user in User.objects.all():  # Запрос отправляется
    print(user.name)

# List comprehension
names = [u.name for u in User.objects.all()]  # Запрос отправляется

# next() с iterator
iterator = iter(User.objects.all())
first_user = next(iterator)  # Запрос отправляется

C. Методы QuerySet, отправляющие запросы:

# count() — отправляет SELECT COUNT(*)
count = User.objects.filter(age__gte=18).count()  # Запрос

# exists() — отправляет SELECT 1 LIMIT 1
has_users = User.objects.filter(age__gte=18).exists()  # Запрос

# get() — отправляет SELECT
user = User.objects.get(id=1)  # Запрос

# first() — отправляет SELECT с LIMIT 1
user = User.objects.first()  # Запрос

# last() — отправляет SELECT с ORDER BY DESC LIMIT 1
user = User.objects.order_by('-id').last()  # Запрос

# latest() / earliest() — отправляет SELECT
user = User.objects.latest('created_at')  # Запрос
user = User.objects.earliest('created_at')  # Запрос

# create() / bulk_create() — отправляет INSERT
user = User.objects.create(name='John', age=25)  # Запрос
users = User.objects.bulk_create([...])  # Запрос

# update() — отправляет UPDATE
User.objects.filter(age__lt=18).update(is_minor=True)  # Запрос

# delete() — отправляет DELETE
User.objects.filter(age__lt=18).delete()  # Запрос

# values() / values_list() — ОТПРАВЛЯЮТ при оценке
result = list(User.objects.values('name', 'age'))  # Запрос
result = User.objects.values_list('name', flat=True)  # Запрос при итерировании

# aggregate() — отправляет SELECT с агрегацией
from django.db.models import Count, Sum, Avg
stats = User.objects.aggregate(Count('id'), Avg('age'))  # Запрос

# annotate() с evaluate
from django.db.models import Count
groups = User.objects.annotate(num_posts=Count('posts'))  # Запрос при оценке
print(list(groups))  # Здесь отправляется запрос

# raw() — отправляет запрос
users = User.objects.raw('SELECT * FROM users WHERE age > %s', [18])  # Запрос при оценке

3. Методы, которые НЕ отправляют запрос (Lazy)

Эти методы возвращают новый QuerySet и не выполняют запрос:

# Фильтрация
qs = User.objects.filter(age__gte=18)  # Нет запроса
qs = qs.filter(is_active=True)  # Нет запроса

# exclude()
qs = User.objects.exclude(status='banned')  # Нет запроса

# order_by()
qs = User.objects.order_by('-created_at')  # Нет запроса

# select_related() / prefetch_related()
qs = User.objects.select_related('profile')  # Нет запроса
qs = User.objects.prefetch_related('posts')  # Нет запроса

# distinct()
qs = User.objects.distinct()  # Нет запроса

# reverse()
qs = User.objects.order_by('-id').reverse()  # Нет запроса

# only() / defer()
qs = User.objects.only('name', 'age')  # Нет запроса
qs = User.objects.defer('description')  # Нет запроса

# using() — выбор БД
qs = User.objects.using('secondary_db').all()  # Нет запроса

# values() без оценки
qs = User.objects.values('name')  # Нет запроса
result = list(qs)  # Запрос отправляется здесь

4. Практический пример: оптимизация

# ПЛОХО — N+1 запросы
users = User.objects.all()  # Запрос 1
for user in users:
    print(user.profile.bio)  # Запросы 2, 3, 4... (N запросов)

# ХОРОШО — используем select_related
users = User.objects.select_related('profile')  # QuerySet (нет запроса)
print(list(users))  # 1 запрос с JOIN
for user in users:
    print(user.profile.bio)  # Нет дополнительных запросов!

# ПЛОХО — запрос за каждый count
total_users = User.objects.count()  # Запрос 1
total_active = User.objects.filter(is_active=True).count()  # Запрос 2
total_banned = User.objects.filter(status='banned').count()  # Запрос 3

# ХОРОШО — один aggregate запрос
from django.db.models import Count, Q
stats = User.objects.aggregate(
    total=Count('id'),
    active=Count('id', filter=Q(is_active=True)),
    banned=Count('id', filter=Q(status='banned'))
)
print(stats)  # 1 запрос с множественной агрегацией

5. Проверка отправляемых запросов

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

# Способ 1: через connection.queries (в DEBUG=True)
User.objects.all().count()
print(connection.queries)  # Список всех выполненных запросов

# Способ 2: CaptureQueriesContext
with CaptureQueriesContext(connection) as context:
    users = list(User.objects.all())  # Запрос здесь
    print(f"Отправлено {context.captured_queries.__len__()} запросов")
    for query in context.captured_queries:
        print(query['sql'])

# Способ 3: django-debug-toolbar (для разработки)
# pip install django-debug-toolbar
# Показывает все запросы в браузере

# Способ 4: explain() — объяснение плана запроса
qs = User.objects.filter(age__gte=18)
print(qs.explain())  # EXPLAIN для PostgreSQL

6. Важные детали

slicing — частично оценивает:

qs = User.objects.all()  # Нет запроса
first_10 = qs[:10]  # Нет запроса (QuerySet)

for user in first_10:  # Запрос LIMIT 10
    print(user)

# НО это отправляет запрос и потом срезает в памяти (плохо)
first_10 = list(User.objects.all())[0:10]  # Загружает ВСЕ пользователей!

Индексация:

qs = User.objects.all()

# Нет запроса
first = qs[0]  # Это НЕ отправляет запрос, возвращает QuerySet

# Запрос отправляется при доступе
first_user = User.objects.all()[0]  # Эквивалент first()

Кэширование:

qs = User.objects.all()

# Первая оценка
count = len(qs)  # Запрос отправляется, результат кэшируется

# Вторая оценка
for user in qs:  # Используется кэш, не новый запрос
    print(user)

# НО это верно только если QuerySet не изменился
qs2 = User.objects.all()  # Новый QuerySet
first_count = len(qs2)  # Запрос 1
qs2.filter(is_active=True)  # Изменение QuerySet
second_count = len(qs2)  # Запрос 2 (кэш инвалидирован)

7. Таблица методов

МетодОтправляет запрос?SQL операция
filter()НетWHERE
exclude()НетWHERE NOT
order_by()НетORDER BY
values()Нет (при оценке Да)SELECT
annotate()Нет (при оценке Да)GROUP BY
select_related()НетJOIN (при оценке)
prefetch_related()НетSeparate queries
count()ДаCOUNT(*)
exists()ДаEXISTS
get()ДаSELECT (LIMIT 1)
first() / last()ДаSELECT LIMIT 1
latest() / earliest()ДаSELECT ORDER BY
create()ДаINSERT
update()ДаUPDATE
delete()ДаDELETE
bulk_create()ДаINSERT

8. Лучшие практики

# ✅ Используй exists() вместо count() для проверки
if User.objects.filter(username='john').exists():
    # COUNT оптимизирован до EXISTS
    pass

# ❌ Не делай так
if User.objects.filter(username='john').count() > 0:
    # Считает ВСЕ совпадения вместо проверки существования
    pass

# ✅ Используй select_related для ForeignKey
users = User.objects.select_related('profile').all()

# ✅ Используй prefetch_related для ManyToMany
posts = Post.objects.prefetch_related('comments').all()

# ✅ Используй only/defer для больших полей
users = User.objects.only('name', 'email').all()

# ✅ Используй values для простых списков
names = User.objects.values_list('name', flat=True)

# ✅ Используй bulk_create для вставки множества
users = User.objects.bulk_create([User(...), User(...)])

Итог: QuerySet в Django использует ленивое вычисление. Запросы отправляются при: list(), tuple(), итерировании, count(), exists(), get(), first(), last(), create(), update(), delete(), values() при оценке. Все остальные методы просто строят новый QuerySet без запроса. Понимание этого критично для написания эффективного кода и избежания N+1 проблем.

Какие методы QuerySet отправляют запросы к БД? | PrepBro