Оптимизация SQL-нагрузки
Крупная E-commerce платформа
Введение
Клиент обратился с задачей: ускорить загрузку одной из ключевых страниц сайта, где наблюдались задержки вплоть до нескольких секунд. После первичного аудита стало очевидно, что источник проблемы — чрезмерная нагрузка на базу данных: при открытии страницы выполнялось более 16 тысяч SQL-запросов.
Такая нагрузка не только снижала производительность, но и создавала риски перебоев при увеличении трафика. Требовалось в сжатые сроки провести оптимизацию, при этом не затронув основную бизнес-логику и не нарушив функционирование других разделов сайта.
Наша команда провела точечную оптимизацию архитектуры загрузки данных и добилась результата: всего 24 запроса вместо 16 685. Работа заняла не более полутора часов.
Описание системы
Проект представляет собой многофункциональную e-commerce платформу, реализованную на фреймворке Laravel 10 с использованием Eloquent ORM. В качестве базы данных используется MySQL. Клиентская часть построена на Blade и частично — на Vue.js.
Страница, вызвавшая затруднение, выводит каталог товаров. Каждый товар включает изображения, характеристики, параметры фильтрации, информацию о бренде, наличии, а также персонализированные данные пользователя (например, избранное). При высокой связанности данных и без использования механизмов оптимизации это приводило к лавинообразному росту SQL-запросов.
Проблема заключалась не в объёме самих данных, а в методе их выборки: отсутствовал eager loading, что провоцировало эффект N+1, при котором для каждой записи выполняются дополнительные запросы на связанные таблицы.
Вызов: чрезмерное количество SQL-запросов на странице
После подключения инструментов профилирования (Laravel Debugbar, SQL-логирование) было зафиксировано 16 685 запросов при загрузке одной страницы. Основные причины:
- Запросы к связанным сущностям выполнялись в циклах;
- Не были определены или задействованы связи между моделями;
- В шаблонах активно использовались обращения к связям без предварительной загрузки.
При этом заказчик поставил ряд ограничений:
- Исключить вмешательство в бизнес-логику и API;
- Решение должно быть масштабируемым;
- Необходимо соблюдение стандартов Laravel и отсутствие «обходных» решений вроде ручной агрегации данных.
Решение: внедрение механизма eager loading и оптимизация моделей
Оптимизация была выполнена по следующим направлениям:
Анализ и определение узких мест.
Были проанализированы все модели, задействованные при выводе страницы, и сопутствующая им логика контроллеров и шаблонов.
Корректировка связей в моделях.
В моделях были уточнены отношения (hasMany, belongsTo, hasOne, morphMany и др.). Это позволило однозначно описать связи между таблицами и задать их явную загрузку при выборке.
Внедрение eager loading.
Во всех местах, где ранее использовались отложенные обращения к связанным моделям, были добавлены вызовы with() и load(). Это обеспечило загрузку всех необходимых данных одним SQL-запросом с объединением.
Пример ( codepen ) :
Product::with(['images', 'brand', 'filters', 'favorites'])->get();
Оптимизация шаблонов и логики представления.
Были пересмотрены шаблоны Blade: обращения к связям внутри циклов были исключены, логика рендера упрощена, повторяющиеся запросы устранены.
Тестирование и замеры.
После внедрения изменений были выполнены нагрузочные тесты и повторный аудит SQL-логов. Результат — всего 24 запроса на полную загрузку страницы.