prefetch_related в DjangoНедавно займався код-ревю Django проєктів і постійно бачив одну й ту ж проблему — N+1 запит. Для прикладу, ви робите один запит, щоб дістати 100 оголошень у вашому маркетплейсі, а потім ще 100 додаткових запитів, щоб дістати дані про магазин по кожному оголошенню. Сумарно у вас виходить 101 запит 😨: ваша база навантажена, клієнти довго очікують відповідь від сервера і ви прогріваєте повітря марною роботою.
В Django таку проблему легко може не помітити навіть досвідчений розробник, оскільки в Django ORM об’єкти є “розумними” і якщо ви дістаєте атрибут підв’язаної моделі, то Django зробить додатковий запит, щоб дістати підв’язану модель. Найлегше такі речі віднайти рев’юверу в циклах, де спочатку автор дістає список чогось з бази даних, а потім, обробляючи список у циклі, дістає підв’язану модель:
products = Product.objects.all()
for product in products:
print(product.shop.name)
Але є місця, де цикли заховані в самому Django і в такому випадку приходиться уважно передивлятися як отриманий список об’єктів обробляється до самого кінця запиту. На прикріпленому фото канонічний приклад такого захованого циклу.
Найлегше вирішити цю проблему, додавши .
prefetch_related до запиту, який дістає список чогось (або .
select_related, якщо ви розумієте різницю між цими методами). Метод .
prefetch_related зробить додатковий запит і дістане всі підв’язані моделі одним запитом і покладе у внутрішній кеш. Унаслідок чого Django зробить два запити: один, щоб дістати оголошення, а один, щоб дістати магазини :
Products.objects \
.prefetch_related('shop') \
.all()
Щоб уникнути N+1 запиту в майбутньому, треба, в першу чергу, знати про таку проблему, можна спробувати тестувати кількість запитів або, найпростіше, при локальній розробці запускати базу в режимі логування всіх запитів і уважно слідкувати, щоб там не було сотні однотипних запитів.