Django select related. Оптимизируем запросы

02 Ноя 2020 , 7768

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, но эта тема для другого поста

comments powered by Disqus

Подписка

Подпишитесь на наш список рассылки, чтобы получать обновления из блога

Рубрики

Теги