Django select related. Оптимизируем запросы
Django ORM - это прекрасный инструмент , который позволяет нам работать с базой данных и при этом не используя "голые" запросы. Из-за этого начинающие разработчики допускают ошибки.
Рассмотрим самый распространенный и классический случай.
У нас есть модель Book (таблица с книгами) и модель Category(таблица с категориями). И каждая книга относится к определенной категории.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=1024)
file = models.FileField(upload_to='books')
checksum = models.CharField(max_length=100, unique=True)
category = models.ForeignKey(
Category, related_name='books',
on_delete=models.SET_NULL,
null=True, blank=True
)
def __str__(self):
return self.name
Давайте выведем на странице названия книг и при этом выведем название категории
Напишем вот такой код вьюхи
from django.shortcuts import render
from django.views.generic import View
from books.models import Book
class IndexView(View):
def get(self, request, *args, **kwargs):
ctx = {}
ctx['books'] = Book.objects.all()
return render(request, 'books/index.html', ctx)
Напишем следующий код шаблона
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Книги</title>
</head>
<body>
<h1>Книги</h1>
{% for book in books %}
<h5>{{ book.name }}</h5>
<span>{{ book.category.name}}</span>
{% endfor %}
</body>
</html>
Названия книг выводятся и названия соответсвующих категорий тоже. Все прекрасно и все счастливы. Но все ли так гладко и прекрасно ?
Давайте посмотрим через django-debug-toolbar сколько запросов генерируется Django ORM
Как вы можете заметить , у нас 11 запросов к базе данных!!!!.Для каждой книги , чтобы получить название категории выполняется новый запрос к базе данных. Как нетрудно догадаться, если мы выведем 30 книг на странице , то у нас будут 30 лишних запросов к базе данных
При выполнении следующего кода в шаблоне для каждой книги выполняется новый запрос к базе данных
<span>{{ book.category.name}}</span>
Но мы же можем получить названия категорий для каждой книги одним запросом. Для этого мы можем использовать select_related
book = Book.objects.select_related('category').all()
Таким образом , при использовании select_related мы одним запросом получаем названия категории. Этот код генерирует следующий sql код
SELECT
"books_book"."id",
"books_book"."name",
"books_book"."file",
"books_book"."checksum",
"books_book"."category_id",
"books_category"."id",
"books_category"."name"
FROM
"books_book"
LEFT OUTER JOIN
"books_category" ON ("books_book"."category_id" = "books_category"."id");
select_related - используется для получения связанных объектов при выполнении запроса. select_related можно использовать только для связи многое-к-одному(ForeignKey) и для связи один-к-одному(OneToOneField).
А для связей многое-к-многим(ManyToManyField) select_related нельзя использовать.Для таких связей используется prefetch_related, но эта тема для другого поста