Решение проблемы N+1 на примере Laravel Eloquent ORM

Решение проблемы N+1 на примере Laravel Eloquent ORM

Одной из распространенных проблем программистов при создании приложения с помощью ORM является N + 1 запрос в их приложении. 

Проблема N+1 — это неэффективный способ запроса базы данных, когда наше приложение делает множество дозапросов к связным моделям. Проблема чаще возникает когда мы получаем список сущностей без реализации метода ленивой загрузки. К счастью, Laravel с его Eloquent имеет инструменты ленивой загрузки, которыми мы можем указать, какие связные сущности мы можем сразу включать в запрос.

Простой пример использования ленивой загрузки. У нас есть модели пользователя и статей. У пользователя есть связь 1 к М со статьями.

Отношение 1 к М между Пользователем и статьями

Сделаем простую страницу, на которой будем выводить все статьи пользователей

Определение маршрута и действия для тестовой страницы

В шаблоне мы выведем всех пользователей и по одной самой первой статье каждого пользователя

Наша тестовая страница, чтобы показать список пользователей с их первыми названиями статей

И когда наша страница будет открыта, мы увидим следующий список запросов

Запросы на тестовой странице

Как мы видим, было выполнено 11 запросов, из которых 1 для получения списка пользователей и 10 для получения первой статьи по каждому пользователю. Это условие называется N+1 проблемой запроса.

Решение задачи запроса N+1 с жадной загрузкой

Если не решать эту проблему, мы можем столкнуться с гораздо бОльшим кол-вом запросов и даже экспоненциальному их росту.

Жадноя загрузка — это процесс, при котором запрос для одного типа сущности также загружает связанные сущности как часть запроса. В Laravel мы можем загружать соответствующие модельные данные с помощью with(..) метода. В нашем примере мы должны изменить наш код со следующими изменениями:

Добавление жадной загрузки статей при загрузке пользователей

И, наконец, мы можем уменьшить кол-во наших запросов до всего лишь 2 запросов:

Производительность нашей страницы после корректировки

Мы также можем создать связь hasOne с соответствующим запросом для извлечения первой статьи пользователя:

Отношение hasOne для извлечения первой статьи пользователя

Тогда мы можем загрузить его, когда мы загружаем наших пользователей:

Жадная загрузка и связь first_article

Вот вам и результат:

Наша страница с использованием жадной загрузки с hasOne связью

Отлично. Мы сократили кол-во запросов с 11 до 2. Мы решили проблему N+1. Но есть нюанс =) Обратите внимание на кол-во загруженных моделей. С 20 их кол-во внезапно увеличилось до 10010.

Динамические связи и жадная загрузка

Основные цели при создании веб-приложения:

  1. минимум запросов к базе данных
  2. минимум использованной памяти

В примерах выше мы смогли сохранить либо малое кол-во запросов, либо малое использование памяти.

Для решения возникшей проблемы мы можем воспользоваться динамической жадной загрузкой через подзапросы.

Ниже приведен пример извлечения данных пользователей с соответствующим идентификатором первой статьи в рамках подзапроса:

Вложенный запрос на пользователей для выбора первого идентификатора статьи каждого пользователя
Добавление подзапроса select к запросу пользователей из модели eloquent

После этого мы можем использовать first_article_id с отношением belongs_to для извлечения первых статей пользователя. Чтобы сделать наш код более понятным, мы также можем использовать Eloquent scopes для инкапсуляции нашего запроса и жадной загрузки для первой статьи. Таким образом, мы должны добавить код ниже в нашу модель пользователя:

Наша пользовательская модель с динамическими отношениями

И наконец, давайте изменим наше действие и посмотрим, что получилось. Мы должны использовать scope в нашем действии, чтобы жадно загрузить первую статью. И тогда мы можем непосредственно обращаться к атрибуту first_article в представлении.

Извлечение пользователей с первой статьей
Вызов первой статьи в представлении

Вот вам и результат:

Результат реализации динамической взаимосвязи нетерпеливой нагрузки

Теперь на нашей странице всего 2 запроса и 20 загруженных моделей. И мы достигли наших целей, оптимизируя запросы к базе данных и использование памяти до минимума.

Остались вопросы?

admin

admin

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

%d такие блоггеры, как: