Eager Loading در Laravel — راهنمای کامل رفع N+1 Query
صفحه لیست مقالات ۲۰۰ میلیثانیه طول میکشد؛ همان صفحه با N+1 ناگهان ۲ ثانیه میشود — مشتری نمیفهمد، فقط «سایت کند است». در ۷۰٪ code reviewهای junior، N+1 اولین گلوگاه است. خبر خوب: در Eloquent با with() حل میشود اگر relation درست تعریف شده باشد.
N+1 دقیقاً چیست؟
۱ query برای لیست Post + N query برای category هر post = N+1. در Blade:
@foreach($posts as $post)
{{ $post->category->name }}
@endforeach
بدون eager load، هر iteration یک SELECT به categories میزند.
راهحل: with()
$posts = Post::query()
->with(['category:id,name,slug', 'user:id,name'])
->published()
->latest()
->paginate(15);
فقط ستونهای لازم را در nested select بیاورید — memory کمتر.
چند سطح تودرتو
Post::with(['comments.user', 'tags'])->get();
withCount — بدون بارگذاری همه رکوردها
Category::withCount('posts')->orderByDesc('posts_count')->get();
برای badge «تعداد مقاله» روی دسته عالی است.
load() و loadMissing()
اگر قبلاً collection دارید:
$posts->load('category');
$posts->loadMissing('tags');
Lazy Eager Loading (Laravel 8+)
Model::preventLazyLoading(! app()->isProduction());
// یا در AppServiceProvider boot — فقط dev
در development exception میگیرید اگر relation بدون with لود شود — عادت خوب میسازد.
تشخیص N+1
- Laravel Debugbar — تب queries
- Telescope
DB::listen()در local- MySQL slow query log
جزئیات بیشتر: بهینهسازی کوئری.
API و Resource
// PostResource — whenLoaded
'category' => CategoryResource::make($this->whenLoaded('category')),
از serialize کردن relation لودنشده جلوگیری کنید.
whereHas بدون eager
فیلتر relation تعداد query را زیاد میکند — بعد از فیلتر with بزنید:
Post::whereHas('tags', fn ($q) => $q->where('slug', 'laravel'))
->with('tags')
->get();
اشتباهات
- with() روی relation تعریفنشده
- over-eager: لود relation استفادهنشده
- فراموش pagination با with سنگین
جمعبندی
قبل از merge هر لیستی در view، بپرسید: چه relationهایی نمایش داده میشود؟ همانها with. روابط، Scope.