21.01.2026

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.