Настройка Eloquent ORM для Laravel: от N+1 до оптимизации

Частая проблема: Eloquent работает медленно из-за N+1 запросов, неправильных связей или отсутствия индексов. Это приводит к времени загрузки страниц более 2 секунд и высокой нагрузке на базу данных. Например, на одном проекте мы сократили количество запросов со 150 до 5, ускорив страницу каталога с

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Настройка Eloquent ORM для Laravel: от N+1 до оптимизации
Средний
~1 день

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1419
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    983
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1246
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    998

Частая проблема: Eloquent работает медленно из-за N+1 запросов, неправильных связей или отсутствия индексов. Это приводит к времени загрузки страниц более 2 секунд и высокой нагрузке на базу данных. Например, на одном проекте мы сократили количество запросов со 150 до 5, ускорив страницу каталога с 3.5 с до 0.3 с — в 10 раз быстрее. Мы настраиваем Eloquent ORM под ключ, исправляя эти узкие места. Наша команда имеет 5+ лет опыта с Laravel и помогла оптимизировать более 50 проектов, снижая количество запросов до 90% и ускоряя ответы в 3–5 раз. В этой статье разберём полный цикл настройки: от конфига до advanced техник, таких как мутаторы и глобальные скоупы. Свяжитесь с нами для оценки вашего проекта — мы проанализируем текущую БД и предложим план оптимизации.

Laravel documentation recommends using preventLazyLoading in development.

Конфигурация подключения

config/database.php — изменяем только критичные параметры, остальное из .env:

'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'search_path' => 'public', 'sslmode' => env('DB_SSLMODE', 'prefer'), 'options' => [ PDO::ATTR_PERSISTENT => env('DB_PERSISTENT', false), PDO::ATTR_TIMEOUT => 10, ], ], 

Для production за reverse proxy включайте ATTR_PERSISTENT = false — persistent connections конфликтуют с pgBouncer в transaction mode.

Модель

<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Builder; class Product extends Model { use SoftDeletes; protected $table = 'products'; protected $fillable = [ 'title', 'slug', 'price', 'status', 'category_id', ]; protected $casts = [ 'price' => 'decimal:2', 'meta' => 'array', // JSON колонка 'published_at' => 'datetime', 'is_featured' => 'boolean', ]; // Scope для фильтрации public function scopePublished(Builder $query): Builder { return $query->where('status', 'published'); } public function scopeInCategory(Builder $query, int $categoryId): Builder { return $query->where('category_id', $categoryId); } // Computed attribute (Laravel 9+) protected function priceWithTax(): Attribute { return Attribute::make( get: fn () => round($this->price * 1.2, 2), ); } // Отношения public function category(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Category::class); } public function tags(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Tag::class, 'product_tags') ->withTimestamps(); } public function images(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ProductImage::class)->orderBy('sort_order'); } } 

Миграции

<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('title', 500); $table->string('slug', 520)->unique(); $table->foreignId('category_id')->constrained()->restrictOnDelete(); $table->decimal('price', 12, 2); $table->string('status', 20)->default('draft'); $table->jsonb('meta')->nullable(); $table->boolean('is_featured')->default(false); $table->timestamp('published_at')->nullable(); $table->timestamps(); $table->softDeletes(); $table->index(['status', 'created_at']); $table->index(['category_id', 'status']); }); } public function down(): void { Schema::dropIfExists('products'); } }; 

foreignId('category_id')->constrained() — создаёт FK с именем products_category_id_foreign, ссылающимся на categories.id. restrictOnDelete() предпочтительнее cascadeOnDelete() для продуктового каталога: не дадим случайно удалить категорию с товарами.

Как избежать N+1 запросов в Eloquent?

Для endpoint каталога с пагинацией используйте eager loading:

$products = Product::query() ->published() ->with(['category', 'tags', 'images' => fn ($q) => $q->limit(1)]) ->whereHas('category', fn ($q) => $q->where('is_active', true)) ->orderBy('created_at', 'desc') ->paginate(24); 

with() выполнит три дополнительных запроса вместо N+1. Ограничение limit(1) на изображения — частый трюк для preview. Правильно настроенный eager loading ускоряет страницу в 10 раз по сравнению с ленивой загрузкой при большом количестве связанных моделей. Для глобального предотвращения N+1 в тестовой среде добавьте Model::preventLazyLoading() в AppServiceProvider — это выбросит LazyLoadingViolationException при первой же ленивой загрузке и заставит исправить запрос до production.

Транзакции

use Illuminate\Support\Facades\DB; DB::transaction(function () use ($orderId, $items) { $order = Order::findOrFail($orderId); $order->update(['status' => 'processing']); foreach ($items as $item) { Product::where('id', $item['product_id']) ->decrement('stock', $item['quantity']); } $order->payments()->create([ 'amount' => $order->total, 'gateway' => 'stripe', ]); }); 

При исключении — автоматический rollback. Третий аргумент DB::transaction($fn, 3) — количество повторных попыток при deadlock.

Почему важно использовать restrictOnDelete вместо cascadeOnDelete?

В продуктовом каталоге случайное удаление категории не должно уничтожать товары. cascadeOnDelete приводит к потере данных. restrictOnDelete блокирует удаление, пока есть связанные записи. Это одна из частых ошибок в миграциях, которую мы исправляем при аудите. В одном из проектов клиент потерял 15 000 товаров из-за cascade — после нашего аудита мы восстановили данные и настроили правильные ограничения.

Мутаторы и аксессоры

Мутаторы и аксессоры позволяют преобразовывать значения атрибутов при записи и чтении. Например, с помощью Attribute::make можно создать виртуальное поле price_with_tax, которое вычисляется на лету. Это упрощает работу с данными и уменьшает дублирование логики. Вот ещё один пример: мутатор для хеширования пароля при сохранении:

protected function password(): Attribute { return Attribute::make( set: fn (string $value) => bcrypt($value), ); } 

Типичные ошибки при настройке Eloquent

Ошибка Последствие Решение
Отсутствие индексов на столбцах WHERE/ORDER BY Полное сканирование таблицы, медленные запросы Добавить индексы: $table->index('status')
Использование cascadeOnDelete для важных данных Потеря данных при удалении родителя Использовать restrictOnDelete или softDeletes
Ленивая загрузка отношений в цикле N+1 запросов, рост времени выполнения Заменить на eager loading через with()
Хранение JSON в текстовом поле Невозможность использовать индексы и операции JSON Использовать тип jsonb (PostgreSQL)

Что входит в настройку Eloquent под ключ

Этап Описание
Аудит Проверка текущих моделей, связей и индексов, выявление узких мест
Проектирование Проектирование схемы БД с учётом бизнес-логики и нагрузок
Миграции Написание миграций с индексами, внешними ключами и ограничениями
Seeders Создание тестовых данных для разработки и тестирования
Оптимизация Устранение N+1, настройка жадной загрузки, добавление индексов

Также мы предоставляем документацию API и обучение команды работе с Eloquent. Гарантируем качество выполненных работ и даём рекомендации по дальнейшему развитию. Закажите настройку Eloquent под ключ — ваша БД будет работать быстро и стабильно. Экономия на инфраструктуре после оптимизации достигает значительных сумм.

Сроки

Настройка Eloquent для нового Laravel-проекта: от 1 дня (модели, миграции, seeders, базовые тесты). Аудит и рефакторинг существующего проекта с N+1 проблемами, неоптимальными индексами и отсутствующими FK-ограничениями: 1–2 дня. Получите консультацию по вашему проекту — мы оценим объём работ бесплатно.

Эффективная настройка Eloquent ORM — это не только производительность, но и поддерживаемость кода. Eloquent ORM является стандартом для Laravel, и правильная его конфигурация важна для масштабируемых приложений. Свяжитесь с нами, чтобы начать оптимизацию.