JavaRush /Курсы /Модуль 3: Django /Использование select_related() и prefetch_related()

Использование select_related() и prefetch_related()

Модуль 3: Django
9 уровень , 6 лекция
Открыта

Работа с реляционными базами данных — это часто балансирование на грани между удобством программирования и производительностью. Предположим, у нас есть две модели: Author и Book. Один автор может написать много книг. Если бы мы просто загружали объекты без оптимизации, каждое обращение к связанному объекту могло бы порождать отдельный запрос к базе данных. Это не проблема, если у нас пара записей. Но попробуйте загрузить сразу тысячу книг с их авторами, и вы услышите, как ваша база данных жалобно стонет.

1. Оптимизация запросов с помощью select_related()

select_related() используется для оптимизации SQL-запросов при работе с полями ForeignKey или OneToOne. Этот метод позволяет объединить две таблицы в один SQL-запрос с помощью операции JOIN, тем самым сокращая количество запросов к базе.

Звучит сложно? Давайте разберем на примере.

Пример без select_related()

# models.py
class Author(models.Model):
    name = models.CharField(max_length=255)

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

# views.py
books = Book.objects.all()
for book in books:
    print(book.title, book.author.name)

В этом примере для каждого объекта book Django отправляет отдельный запрос к базе данных, чтобы получить данные об авторе. Если у нас 100 книг, это означает 101 запрос (1 для книг + 100 для каждого автора).

Пример с select_related()

books = Book.objects.select_related('author')
for book in books:
    print(book.title, book.author.name)

Теперь Django выполнит один запрос с использованием SQL JOIN, извлекая данные о книгах и их авторах одновременно. Выглядит это примерно так:

SELECT book.id, book.title, author.id, author.name
FROM book
INNER JOIN author ON book.author_id = author.id;

Когда использовать select_related()?

  • Если у вас связь ForeignKey или OneToOne.
  • Если вы многократно обращаетесь к связанным объектам в цикле или других операциях.
  • Если ваши данные всегда нужны вместе (например, книга всегда отображается с её автором).

2. Работа с prefetch_related()

Если select_related() удобен для связей один-ко-многим ForeignKey и OneToOne, то для связей многие-ко-многим (ManyToMany) или сложных запросов на множество связанных объектов лучше подходит prefetch_related().

Этот метод делает два отдельных запроса, но кэширует результат, чтобы Django могла связать объекты на уровне Python. Это позволяет избежать избыточного количества запросов при сложных связях.

Пример без prefetch_related()

class Tag(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    title = models.CharField(max_length=255)
    tags = models.ManyToManyField(Tag)

# views.py
books = Book.objects.all()
for book in books:
    print(book.title, [tag.name for tag in book.tags.all()])

Для каждой книги Django выполнит отдельный запрос, чтобы загрузить связанные теги. Если у нас 50 книг и каждая имеет 5 тегов, мы получим 51 запрос (1 для книг + 50 для тегов).

Пример с prefetch_related()

books = Book.objects.prefetch_related('tags')
for book in books:
    print(book.title, [tag.name for tag in book.tags.all()])

Теперь выполняются только два запроса: один для загрузки книг, другой для загрузки всех тегов, связанных с этими книгами.

Пример SQL-запросов:

  1. Загрузка книг:
       SELECT * FROM book;
    
  1. Загрузка тегов:
    SELECT * FROM tag
    INNER JOIN book_tags ON tag.id = book_tags.tag_id
    WHERE book_tags.book_id IN (1, 2, 3, ..., 50);
    

Когда использовать prefetch_related()?

  • Если у вас связь ManyToMany или обратная связь ForeignKey.
  • Если вы хотите избежать большого количества "мелких" запросов.
  • Если вам нужно объединить данные из связанных таблиц на уровне Python.

3. Сравнение select_related() и prefetch_related()

Метод Тип связи Количество запросов Преимущества Недостатки
select_related() ForeignKey, OneToOne 1 запрос Объединяет данные на уровне базы данных с помощью SQL JOIN Не поддерживает ManyToMany
prefetch_related()| ManyToMany, обратный ForeignKey 2 запроса (минимум) Загружает данные через отдельные запросы и связывает их на уровне Python Выполняет больше запросов, чем select_related()

Используйте select_related(), если вам нужно просто подтянуть данные для полей ForeignKey или OneToOne, а prefetch_related() — для более сложных сценариев, связанных с ManyToMany или обратной связью.

4. Комбинирование select_related() и prefetch_related()

Иногда одно без другого не обходится. Например, если у нас есть связь вида:

  • Книга (Book) -> Автор (Author) через ForeignKey.
  • Книга также может иметь теги (ManyToMany).

Мы можем использовать оба метода:

books = Book.objects.select_related('author').prefetch_related('tags')
for book in books:
    print(f"Книга: {book.title}, Автор: {book.author.name}, Теги: {[tag.name for tag in book.tags.all()]}")

5. Практика

Давайте создадим небольшой пример. У нас есть три модели: Author, Book и Tag.

Модели

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

class Tag(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag)

Загрузка данных без оптимизации

books = Book.objects.all()
for book in books:
    print(book.title, book.author.name, [tag.name for tag in book.tags.all()])

Результат: огромное количество запросов (один для книг, по одному для каждого автора и для тегов).

Оптимизированный вариант

books = Book.objects.select_related('author').prefetch_related('tags')
for book in books:
    print(book.title, book.author.name, [tag.name for tag in book.tags.all()])

Результат: всего три запроса. Очень элегантно и быстро!

Итоги

Методы select_related() и prefetch_related() — это ключевые инструменты в оптимизации запросов Django ORM. Они помогут вам избежать типичных ошибок начинающих разработчиков, таких как "N+1 запрос". Используйте их правильно, и ваш сервер (а, возможно, и фронтенд) будет вам благодарен.

1
Задача
Модуль 3: Django, 9 уровень, 6 лекция
Недоступна
Использование select_related
Использование select_related
1
Задача
Модуль 3: Django, 9 уровень, 6 лекция
Недоступна
Использование prefetch_related
Использование prefetch_related
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ