Laravel’de SQL Performansı: Eloquent’ta N+1 Sorununu Yakala, Query’yi Şekillendir
Laravel’de N+1 sorununu tespit edip eager loading, select, withCount ve chunk ile performansı iyileştirin.
N+1 Nedir ve Neden Başınıza Bela Olur?
Eloquent ilişkileri kullanırken en sık performans düşmanı N+1 problemidir. Bir liste çeker, her satır için ayrı bir ilişki sorgusu çalıştırırsınız. Lokal ortamda fark etmez; veri büyüyünce hem DB hem uygulama nefes alamaz.
Örnek:
$posts = Post::latest()->take(50)->get();
foreach ($posts as $post) {
echo $post->author->name; // her post için ekstra query
}
Bu yapı: 1 (postlar) + 50 (yazarlar) query demektir.
1) N+1’i Gözle Gör: Query Log ve Debugbar
Basitçe yakalamak için:
DB::enableQueryLog();
// ... kod
dd(DB::getQueryLog());
Daha pratik olarak geliştirme ortamında Laravel Debugbar/Telescope gibi araçlarla sayıyı ve süreyi anlık izleyebilirsiniz (prod’da açmayın).
2) Eager Loading ile Tek Hamlede Çöz
En temel çözüm: with().
$posts = Post::with('author')
->latest()
->take(50)
->get();
foreach ($posts as $post) {
echo $post->author->name;
}
Artık genelde 2 query çalışır: postlar + yazarlar.
Nested eager loading
$posts = Post::with(['author.company'])
->latest()
->take(50)
->get();
3) Gereksiz Kolonları Taşıma: select() ve İlişkide Kolon Kısıtlama
Performans sadece query sayısı değil, taşınan veri de önemli.
$posts = Post::query()
->select(['id', 'user_id', 'title', 'created_at'])
->with(['author:id,name'])
->latest()
->take(50)
->get();
Not: İlişkide kolon kısıtlarken foreign key alanlarının (ör. user_id) doğru şekilde seçildiğinden emin olun.
4) Sayı Lazımsa İlişkiyi Yükleme: withCount()
Sadece yorum sayısını gösterecekseniz tüm yorumları çekmeyin.
$posts = Post::query()
->withCount('comments')
->latest()
->paginate(20);
// blade
{{ $post->comments_count }}
Benzer şekilde:
->withSum('payments', 'amount')
->withAvg('ratings', 'score')
5) Büyük Veri İşlemede Belleği Koru: chunk() / lazy()
10.000 kaydı get() ile almak yerine parça parça işleyin.
Post::query()
->where('is_archived', true)
->chunk(500, function ($posts) {
foreach ($posts as $post) {
// işlem
}
});
Streaming tarzı:
foreach (Post::where('is_archived', true)->lazy() as $post) {
// daha düşük bellek
}
6) İlişkiyi Sonradan Yükle: load() ve Koşullu Yükleme
Bazı durumlarda önce ana listeyi çekip sonra ilişkiyi gerekirse yüklemek daha mantıklı:
$posts = Post::latest()->take(50)->get();
if (request('with_author')) {
$posts->load('author');
}
Kapanış: Hedefin “Az Query” Değil, “Doğru Query”
Eager loading ile N+1’i çözün, select() ile payload’ı küçültün, withCount() ile gereksiz ilişki yüklemeyin, büyük dataset’lerde chunk/lazy ile belleği koruyun. Bu dört adım çoğu Laravel projesinde hissedilir bir hız artışı getirir.