2 ноября 2020 г. 18:09

59

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). А для связей многое-к-многим select_related нельзя использовать.Для таких связей используется prefetch_related, но эта тема для другого поста

comments powered by Disqus