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

Зачем нужен select_related в Django?

2.0 Middle🔥 201 комментариев
#Django

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

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

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

Зачем нужен select_related в Django?

select_related() — это метод оптимизации ORM в Django, который решает проблему «N+1 запросов» (N+1 queries problem). Он предварительно загружает связанные объекты через JOIN в одном запросе вместо нескольких отдельных запросов.

Проблема: N+1 запросы

Без select_related() происходит множество запросов:

# Модели
class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

# Плохо: N+1 запросы
books = Book.objects.all()[:10]  # 1 запрос

for book in books:
    print(book.author.name)  # 10 дополнительных запросов!

# Всего: 1 + 10 = 11 запросов к БД

Даже если вы получили 10 книг одним запросом, доступ к book.author требует отдельный запрос для каждой книги.

Решение: select_related()

select_related() загружает связанные объекты с помощью SQL JOIN в одном запросе:

# Хорошо: только 1 запрос
books = Book.objects.select_related('author')[:10]

for book in books:
    print(book.author.name)  # Нет дополнительных запросов!

# Всего: 1 запрос

Django генерирует SQL запрос с JOIN:

SELECT "book"."id", "book"."title", "book"."author_id",
       "author"."id", "author"."name"
FROM "book"
INNER JOIN "author" ON ("book"."author_id" = "author"."id")
LIMIT 10;

Как работает select_related()

# Одно отношение (ForeignKey)
Book.objects.select_related('author')

# Множественные отношения
Book.objects.select_related('author', 'publisher')

# Вложенные отношения (через __)
Book.objects.select_related('author__country')

# Цепочка методов
books = (
    Book.objects
    .select_related('author')
    .select_related('publisher')
    .select_related('author__country')
)

Сравнение производительности

import time

def measure_performance():
    # Без select_related
    start = time.time()
    books = Book.objects.all()[:100]
    for book in books:
        _ = book.author.name
    without_select = time.time() - start
    
    # С select_related
    start = time.time()
    books = Book.objects.select_related('author')[:100]
    for book in books:
        _ = book.author.name
    with_select = time.time() - start
    
    print(f"Без select_related: {without_select:.3f}s (101 запросов)")
    print(f"С select_related: {with_select:.3f}s (1 запрос)")
    print(f"Ускорение: {without_select / with_select:.1f}x")

measure_performance()
# Результат:
# Без select_related: 0.852s (101 запросов)
# С select_related: 0.003s (1 запрос)
# Ускорение: 280x

select_related vs prefetch_related

Для отношений «многие ко многим» (ManyToMany) и reverse ForeignKey используется prefetch_related():

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

class Review(models.Model):
    book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')
    rating = models.IntegerField()

# select_related для ForeignKey (работает с JOIN)
books = Book.objects.select_related('author')

# prefetch_related для ManyToMany и обратные отношения
authors = Author.objects.prefetch_related('book_set')
# или
books = Book.objects.prefetch_related('reviews')

# Сочетание обоих
books = (
    Book.objects
    .select_related('author')  # Прямая связь
    .prefetch_related('reviews')  # Обратная связь
)

Ограничения select_related()

select_related() работает только с отношениями один-к-одному (OneToOneField) и многие-к-одному (ForeignKey):

# Работает
Book.objects.select_related('author')  # ForeignKey
Book.objects.select_related('publisher')  # OneToOneField

# НЕ работает (нужна prefetch_related)
Author.objects.select_related('books')  # обратная связь
Book.objects.select_related('tags')  # ManyToMany

Потому что:

  • ForeignKey = много книг на одного автора → один JOIN
  • ManyToMany = много-ко-многим → нужны дополнительные запросы

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

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)
    supplier = models.ForeignKey('Supplier', on_delete=models.CASCADE)

class Category(models.Model):
    name = models.CharField(max_length=50)
    parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE)

class Supplier(models.Model):
    name = models.CharField(max_length=100)
    country = models.ForeignKey('Country', on_delete=models.CASCADE)

class Country(models.Model):
    name = models.CharField(max_length=50)

# Оптимизированный запрос
products = (
    Product.objects
    .select_related(
        'category',           # Загрузить категорию
        'category__parent',   # Загрузить родительскую категорию
        'supplier',           # Загрузить поставщика
        'supplier__country'   # Загрузить страну поставщика
    )
    .all()[:100]
)

# Теперь эти доступы НЕ создают новые запросы
for product in products:
    print(f"{product.name} - {product.category.name} - {product.supplier.country.name}")

Django Shell: отладка запросов

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

# Без select_related
with CaptureQueriesContext(connection) as context:
    books = Book.objects.all()[:10]
    for book in books:
        _ = book.author.name
    print(f"Запросов: {len(context)}")
    # Запросов: 11

# С select_related
with CaptureQueriesContext(connection) as context:
    books = Book.objects.select_related('author')[:10]
    for book in books:
        _ = book.author.name
    print(f"Запросов: {len(context)}")
    # Запросов: 1

Когда использовать

Используй select_related:

  • Для ForeignKey и OneToOneField
  • Когда точно понадобятся связанные объекты
  • Когда связь один-ко-многим (ForeignKey)
  • Когда нужна максимальная производительность

НЕ используй select_related без необходимости:

  • Если связанные объекты не будут использованы
  • Если много связей и большие JOIN'ы (может быть медленнее)
  • Для ManyToMany или обратных связей (используй prefetch_related)

Выводы

  1. select_related() решает проблему N+1 запросов
  2. Использует SQL JOIN для загрузки связанных объектов
  3. Работает для ForeignKey и OneToOneField
  4. Значительно ускоряет код (10-100x на практике)
  5. Для ManyToMany и обратных связей используй prefetch_related()
  6. Добавляй select_related() на слое Views/Serializers для оптимизации запросов
Зачем нужен select_related в Django? | PrepBro