Почему проблема N+1 в Django ORM так часто упоминается?
Используя Django ORM, вы часто слышите термин "проблема N+1". Однако, если вы не сталкивались с ней напрямую, сложно ощутить, насколько серьезной является эта проблема.
Говоря простыми словами, это "проблема, когда запрос, который должен быть выполнен только один раз, выполняется гораздо чаще, чем ожидалось". В результате, скорость загрузки страниц может значительно замедлиться, и чем больше данных, тем более выражено снижение производительности. Особенно это часто происходит, когда вы запрашиваете данные, связанные с моделями, поэтому это обязательно нужно понимать, когда вы используете ORM.
💡 Давайте легко поймем проблему N+1
Предположим, мы создаем блоговую систему. Каждый автор (Author) может писать несколько постов (Post). Если представить это с помощью модели Django, она будет выглядеть следующим образом.
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)
Так как каждый автор (Author) может писать несколько постов (Post), это отношение 1:N.
Теперь предположим, что мы реализуем функцию, которая выводит имена всех авторов и заголовки их постов. Как правило, это может быть написано следующим образом.
⚠️ Код, вызывающий проблему N+1
authors = Author.objects.all() # (1) Первый запрос (поиск в таблице Author)
for author in authors:
print(author.post_set.all()) # (N) Поиск постов каждого автора по отдельности
Если запустить этот код, возможно, это займет больше времени, чем ожидалось. Если данных не так много, это может и не быть заметным, но если авторов 100, а постов более 500? Проблема начнет проявляться.
🧐 SQL-запросы, которые фактически выполняются
SELECT * FROM author; -- Выполняется 1 раз
SELECT * FROM post WHERE author_id = 1; -- Выполняется N раз
SELECT * FROM post WHERE author_id = 2;
SELECT * FROM post WHERE author_id = 3;
...
Таким образом, чем больше авторов, тем больше становится количество запросов. Вот в чем заключается проблема N+1.
🚀 В каких ситуациях часто возникает проблема N+1?
На самом деле эта проблема возникает независимо от того, хорошо вы используете Django ORM или нет, при запросе данных, связанных с двумя или более моделями, вам почти всегда нужно быть внимательным. Особенно это случается в следующих случаях.
1️⃣ Когда в шаблоне вы ссылаетесь на данные внутри цикла
{% for author in authors %}
{{ author.name }}
{% for post in author.post_set.all %}
- {{ post.title }}
{% endfor %}
{% endfor %}
Когда вы обращаетесь к данным в шаблоне через цикл, каждый объект вызывает дополнительные запросы, что снижает производительность.
2️⃣ Когда в функции представления вы ссылаетесь на связанные модели в цикле
def blog_view(request):
authors = Author.objects.all() # Первый запрос (поиск авторов)
author_list = []
for author in authors:
author_list.append({
'name': author.name,
'posts': author.post_set.all() # Происходит N дополнительных запросов
})
return render(request, 'blog.html', {'authors': author_list})
Если данные обрабатываются в функции представления, такая же проблема также может возникнуть.
💡 Мой личный опыт с проблемой N+1
Ранее я создавал пользовательский метод get_absolute_url()
.
Это была функция, которая генерировала URL, объединяя несколько полей модели,
и я нашел причину медленной работы только после того, как столкнулся с проблемой производительности.
class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def get_absolute_url(self):
return f"/author/{self.author.name}/{self.slug}/"
Каждый раз, когда вы вызываете этот метод, self.author.name
выполняется,
вызывая N дополнительных запросов.
Решение этой проблемы заняло у меня довольно много времени.
✅ Резюме: Почему важно понимать проблему N+1
- Если вы используете Django ORM, эта проблема может возникнуть в любое время.
- Если скорость страницы замедляется, это может означать, что ORM выполняет больше запросов, чем ожидалось.
- Проблема может возникнуть в любом месте: в шаблонах, функциях представления и методах модели.
- С увеличением объема данных производительность может сильно ухудшиться.
- Поскольку ORM не проводит автоматическую оптимизацию, вам нужно проверить выполняемые запросы и оптимизировать их.
✅ В следующей статье мы обсудим, как решить проблему N+1 с помощью select_related
и prefetch_related
.
댓글이 없습니다.