Ответ
В Django ORM SQL-операция JOIN выполняется неявно при обращении к связанным моделям. Для оптимизации запросов и решения проблемы "N+1" существуют два ключевых метода:
-
*`select_related(fields)
** Этот метод "подтягивает" связанные объекты в одном SQL-запросе, используяLEFT OUTER JOIN. Он эффективен для связей "один-к-одному" (OneToOneField) и "многие-к-одному" (ForeignKey`).Проблема (N+1 запросов):
# models.py # class Book(models.Model): # author = models.ForeignKey(Author, on_delete=models.CASCADE) # views.py books = Book.objects.all() # 1-й запрос for book in books: # На каждой итерации выполняется дополнительный запрос к таблице авторов print(book.author.name) # N дополнительных запросовРешение с
select_related:# Выполняется один SQL-запрос с JOIN books = Book.objects.select_related('author').all() for book in books: # Данные об авторе уже загружены, дополнительный запрос не нужен print(book.author.name) -
*`prefetch_related(lookups)
** Этот метод работает иначе: он выполняет отдельный запрос для связанных объектов и "соединяет" их в Python. Идеально подходит для связей "многие-ко-многим" (ManyToManyField) и обратных связейForeignKey`.Пример:
# models.py # class Author(models.Model): ... # class Book(models.Model): # authors = models.ManyToManyField(Author) # views.py # Получаем все книги и для каждой книги всех ее авторов books = Book.objects.prefetch_related('authors').all() for book in books: # authors.all() не генерирует новый запрос к БД author_names = [author.name for author in book.authors.all()] print(f"{book.title}: {', '.join(author_names)}") -
Неявный
JOINчерезfilter()иexclude()Django автоматически создаетJOIN, когда вы фильтруете по полям связанной модели, используя двойное подчеркивание__.# Найти все книги, написанные автором с именем 'Leo Tolstoy' # SQL: SELECT ... FROM book INNER JOIN author ON ... WHERE author.name = 'Leo Tolstoy' books = Book.objects.filter(author__name='Leo Tolstoy')
Сводка:
select_related: дляForeignKeyиOneToOneField(один SQL-запрос сJOIN).prefetch_related: дляManyToManyFieldи обратных связей (отдельный запрос иJOINв Python).filter(): для фильтрации по связанным полям (неявныйJOIN).