Veri katmanı ve SQL
N+1 problemi, Include ve AsNoTracking
Döngü içinde sorgu atmanın gizli maliyeti ve okuma sorgularını hafifletmek.
Kavramı anlayalım
N+1 problemi, bir liste için bir sorgu atıp ardından listedeki her öğe için ayrı bir sorgu daha atmaktır. 20 yazı listelenirken her yazının yazarı ayrı yüklenirse 1 + 20 = 21 sorgu çalışır. Her sorgu tek başına hızlı görünür; ama ağ gidiş-dönüşleri toplandığında sayfa yavaşlar ve veri büyüdükçe sorun katlanır. Geliştirme ortamında az veriyle fark edilmez, üretimde ortaya çıkar.
Çözüm, ilişkili veriyi aynı sorguda getirmektir. Include, navigasyon özelliğini JOIN ile yükler. Okuma için daha iyi yol çoğunlukla projeksiyondur: Select içinde p.Author.Name kullanmak, EF Core’un yalnız gerekli kolonlarla bir JOIN üretmesini sağlar.
Varsayılan olarak EF Core yüklediği entity’leri değişiklik için izler. Yalnız okunacak veride bu boşa harcanan bellek ve işlemcidir. AsNoTracking izlemeyi kapatır; okuma uç noktalarında daha hızlı ve hafiftir. Simülatördeki ‘Yavaş liste: N+1’ senaryosu farkı süre çizelgesinde gösterir.
N+1, üretimdeki en yaygın ORM performans sorunudur ve kodu okuyarak kolayca gözden kaçar.
Lazy loading’i açıp navigasyon özelliklerine döngü içinde erişmek.
Bir mektubun şehirdeki yolculuğu
İsteği bir mektup, sunucuyu bir şehir gibi düşün. Senaryo seç, oynat ve mektubun hangi kapıdan geçip nerede durdurulduğunu izle.
Blog sayfası 20 yazıyı yazar adlarıyla birlikte istiyor.
Aynı konu. Üç farklı derinlik.
// ✗ N+1
var posts = await db.Posts.Take(20).ToListAsync(ct);
foreach (var p in posts)
p.AuthorName = (await db.Authors.FindAsync(p.AuthorId))!.Name; // 20 ek sorguDöngü içindeki her await bir veritabanı gidiş-dönüşüdür.
Bu sayfa gerçek bir sunucuya veya veritabanına bağlanmaz. Kod örnekleri .NET 8, Next.js ve SQL Server için temsilîdir; süreler simülasyondaki örnek değerlerdir, ölçüm değildir. Örnekleri yalnız kendi geliştirme ortamında dene; gerçek bağlantı dizesi, token veya parola paylaşma.
Önce kendin dene.
Bir sipariş sayfası her siparişin müşterisini ve her siparişin ürünlerini gösteriyor. 50 sipariş için SQL günlüğünde 101 sorgu var. Hangi iki ilişki N+1 üretiyor ve tek sorguya nasıl indirirsin?
Çözümü ve açıklamasını göster
1 (siparişler) + 50 (müşteri) + 50 (ürünler) = 101
Düzeltme: projeksiyon
db.Orders.AsNoTracking().Select(o => new OrderView(
o.Id, o.Customer.Name,
o.Items.Select(i => i.Product.Name).ToList()))
// koleksiyon için AsSplitQuery() de değerlendirilebilirHer ilişki için döngüde yükleme N sorgu ekler. Projeksiyon ilişkileri JOIN’e çevirir; büyük koleksiyonlarda bölünmüş sorgu kartezyen patlamayı önler.