Ответ
В 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).
Ответ 18+ 🔞
А, слушай, про эти джойны в Джанго! Ну, это же просто пиздец, как часто народ на грабли наступает, а потом удивляется, почему приложение тормозит, как черепаха в патоках. Сейчас разжую, как бабушка манную кашу.
Представь, у тебя есть модели: Книга и Автор. Книга ссылается на автора через ForeignKey. И вот ты такой пишешь:
books = Book.objects.all() # Всё окей, один запрос
for book in books:
print(book.author.name) # ОПА! А вот тут-то и начинается пиздец!
Что происходит? Первый запрос за книги. А потом на КАЖДОЙ, блядь, итерации цикла — ещё один отдельный запрос в базу, чтобы достать имя автора этой конкретной книги. 100 книг = 101 запрос. Это и есть тот самый "N+1 запрос", от которого у любого нормального бэкендера волосы дыбом встают. Удивление пиздец, да?
Спаситель №1: select_related
Это твой волшебный пинок под зад для связей "один-к-одному" или "многие-к-одному" (то бишь ForeignKey). Он говорит ORM: "Эй, чувак, когда будешь тащить книги, сразу одним махом, одним SQL-запросом, прихвати и данные об авторе, через LEFT OUTER JOIN".
# Вжух! И всё в одном запросе. Никакой лишней возни.
books = Book.objects.select_related('author').all()
for book in books:
print(book.author.name) # Всё уже тут, в оперативке. Быстро, как хуй с горы.
Спаситель №2: prefetch_related
А вот это уже для более сложных отношений — "многие-ко-многим" (ManyToManyField) или когда нужно "в обратную сторону" по связи пойти. Он работает хитрее: делает два (или больше) отдельных запроса, а потом уже в коде на Python аккуратно склеивает их, как хитрая жопа.
Допустим, у книги может быть несколько авторов:
# models.py
# class Book(models.Model):
# authors = models.ManyToManyField(Author)
# views.py
# Сначала запрос за всеми книгами, потом ОТДЕЛЬНЫЙ запрос за ВСЕМИ авторами этих книг.
books = Book.objects.prefetch_related('authors').all()
for book in books:
# И тут магия: book.authors.all() НЕ лезет в базу снова!
author_names = [author.name for author in book.authors.all()]
print(f"{book.title}: {', '.join(author_names)}")
А что с фильтрацией? А тут Джанго сам всё понимает, ёпта! Хочешь найти все книги Толстого? Фильтруй по полю связанной модели, и ORM сам неявно джойн нарисует.
# SQL под капотом будет с INNER JOIN. Всё честно.
books = Book.objects.filter(author__name='Лев Толстой')
Итог, чтобы не ебать мозг:
select_related— твой друг дляForeignKey/OneToOne. Тянет всё в одном запросе. Используй, если знаешь, что будешь лазить по связанным полям.prefetch_related— твой друг дляManyToManyFieldи обратных связей. Делает отдельный запрос, но потом в Python всё собирает. Сложнее, но без него никуда.- Фильтруй через
__— и Джанго сам джойн сделает там, где надо.
Главное — не забывай про эти методы, а то накроешься медным тазом с производительностью. Волнение ебать, когда в логах видишь тысячу одинаковых запросов на одной странице.