Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Tek sorgularla ilgili performans sorunları
İlişkisel veritabanlarında çalışırken EF, JOIN'leri tek bir sorguya ekleyerek ilgili varlıkları yükler. SQL kullanılırken JOIN'ler oldukça standart olsa da, yanlış kullanıldığında önemli performans sorunları oluşturabilir. Bu sayfada bu performans sorunları açıklanır ve bunların etrafında çalışan ilgili varlıkları yüklemek için alternatif bir yol gösterilir.
Kartezyen patlaması
Şimdi aşağıdaki LINQ sorgusunu ve çevrilmiş SQL eşdeğerini inceleyelim:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.Include(b => b.Contributors)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [p].[Id], [p].[BlogId], [p].[Title], [c].[Id], [c].[BlogId], [c].[FirstName], [c].[LastName]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Contributors] AS [c] ON [b].[Id] = [c].[BlogId]
ORDER BY [b].[Id], [p].[Id]
Bu örnekte, hem Posts hem de Contributors, Blog'nin koleksiyon gezintileri olduğundan - aynı düzeyde olduklarından - ilişkisel veritabanları bir çapraz ürün döndürür: Posts içindeki her satır, Contributors içindeki her satır ile birleştirilir. Bu, belirli bir blogda 10 gönderi ve 10 katkıda bulunan varsa veritabanının bu tek blog için 100 satır döndürdüğü anlamına gelir. Bazen kartezyen patlama olarak da adlandırılan bu fenomen, özellikle sorguya daha fazla eşdüzey JOIN eklendikçe çok miktarda verinin istemeden istemciye aktarılmasına neden olabilir; bu, veritabanı uygulamalarında önemli bir performans sorunu olabilir.
İki JOIN aynı düzeyde olmadığında kartezyen patlamanın gerçekleşmediğini unutmayın:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [t].[Id], [t].[BlogId], [t].[Title], [t].[Id0], [t].[Content], [t].[PostId]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Comment] AS [c] ON [p].[Id] = [c].[PostId]
ORDER BY [b].[Id], [t].[Id]
Bu sorguda, Comments bir koleksiyon gezintisidir ve Post'ye yöneliktir; önceki sorgudaki Contributors ise Blog'ye yönelik bir koleksiyon gezintisiydi. Bu durumda, blogda bulunan her yorum için tek bir satır döndürülür (gönderileri aracılığıyla) ve çapraz ürün gerçekleşmez.
Veri yineleme
JOIN'ler başka bir performans sorunu oluşturabilir. Şimdi yalnızca tek bir koleksiyon gezintisini yükleyen aşağıdaki sorguyu inceleyelim:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [b].[HugeColumn], [p].[Id], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
ORDER BY [b].[Id]
Öngörülen sütunlara bakıldığında, bu sorgu tarafından döndürülen her satır hem Blogs hem de Posts tablolarından özellikler içerir; bu da, blog özelliklerinin blogda bulunan her gönderi için yinelendiği anlamına gelir. Bu genellikle normal olsa da ve hiçbir soruna neden olmasa da, tabloda çok büyük bir sütun (örneğin ikili veriler veya çok büyük bir metin) varsa Blogs , bu sütun çoğaltılır ve istemciye birden çok kez geri gönderilir. Bu, ağ trafiğini önemli ölçüde artırabilir ve uygulamanızın performansını olumsuz etkileyebilir.
Eğer gerçekten büyük bir sütuna ihtiyacınız yoksa, sorgulamamak oldukça kolaydır.
var blogs = await ctx.Blogs
.Select(b => new
{
b.Id,
b.Name,
b.Posts
})
.ToListAsync();
İstediğiniz sütunları açıkça seçmek için bir projeksiyon kullanarak büyük sütunları atlayabilir ve performansı geliştirebilirsiniz; Veri yinelemeden bağımsız olarak bunun iyi bir fikir olduğunu unutmayın, bu nedenle koleksiyon gezintisi yüklemediğinde bile bunu yapmayı göz önünde bulundurun. Ancak, bu blogu anonim bir türe projelediğinden, blog EF tarafından izlenmez ve değişiklikleri her zamanki gibi geri kaydedilemez.
Kartezyen patlamanın aksine, yinelenen veri boyutu göz ardı edilebilir olduğundan JOIN'lerin neden olduğu veri yinelemesinin genellikle önemli olmadığını belirtmek gerekir; Bu genellikle yalnızca asıl tablonuzda büyük sütunlar varsa endişelenecek bir şeydir.
Sorguları bölme
Yukarıda açıklanan performans sorunlarına geçici bir çözüm bulmak için EF, belirli bir LINQ sorgusunun birden çok SQL sorgusuna bölünmesi gerektiğini belirtmenize olanak tanır. JoIN'ler yerine bölünmüş sorgular, eklenen her koleksiyon gezintisi için ek bir SQL sorgusu oluşturur:
using (var context = new BloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSplitQuery()
.ToListAsync();
}
Aşağıdaki SQL'i oluşturur:
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]
SELECT [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
Uyarı
10'undan önceki EF sürümlerinde Skip/Take ile bölünmüş sorgular kullanırken, sorgu sıralamanızı tamamen benzersiz hale getirmek için özellikle dikkat edin; bunun yapılmaması yanlış verilerin döndürülmesine neden olabilir. Örneğin, sonuçlar yalnızca tarihe göre sıralanıyorsa ancak aynı tarihe sahip birden çok sonuç olabilirse, bölünmüş sorguların her biri veritabanından farklı sonuçlar alabilir. Hem tarihe hem de kimliğe göre (veya başka bir benzersiz özelliğe veya özellik birleşimine göre) sıralama tamamen benzersiz hale gelir ve bu sorundan kaçınır. İlişkisel veritabanlarının, birincil anahtara bile varsayılan olarak herhangi bir sıralama uygulamadığını unutmayın.
Not
Bire bir ilgili varlıklar her zaman aynı sorgudaki JOIN'ler aracılığıyla yüklenir, bu nedenle performans etkisi yoktur.
Bölünmüş sorguları genel olarak etkinleştirme
Ayrıca, bölünmüş sorguları uygulamanızın bağlamı için varsayılan olarak yapılandırabilirsiniz:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0",
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}
Bölünmüş sorgular varsayılan olarak yapılandırıldığında, belirli sorguları tek sorgu olarak yürütülecek şekilde yapılandırmak mümkündür:
using (var context = new SplitQueriesBloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSingleQuery()
.ToListAsync();
}
EF Core, yapılandırma olmadığında varsayılan olarak tek sorgu modunu kullanır. Performans sorunlarına neden olabileceğinden EF Core, aşağıdaki koşullar karşılandığında bir uyarı oluşturur:
- EF Core, sorgunun birden çok koleksiyon yüklediğini algılar.
- Kullanıcı sorgu bölme modunu genel olarak yapılandırmadı.
- Kullanıcı sorguda işleci kullanmadı
AsSingleQuery/AsSplitQuery.
Uyarıyı kapatmak için, sorgu bölme modunu genel olarak veya sorgu düzeyinde uygun bir değerle yapılandırın.
Bölünmüş sorguların özellikleri
Bölünmüş sorgu JOIN'ler ve kartezyen patlamayla ilgili performans sorunlarını önlerken bazı dezavantajları da vardır:
- Veritabanlarının çoğu tek sorgular için veri tutarlılığını garanti ederken, birden çok sorgu için böyle bir garanti yoktur. Sorgularınızı yürütürken veritabanı eşzamanlı olarak güncelleştiriliyorsa sonuçta elde edilen veriler tutarlı olmayabilir. Sorguları serileştirilebilir veya anlık görüntü işlemine sarmalayarak bunu azaltabilirsiniz, ancak bunu yapmak kendi performans sorunları da oluşturabilir. Daha fazla bilgi için veritabanınızın belgelerine bakın.
- Her sorgu halen veritabanınıza ek bir ağ gidiş dönüşü anlamına gelir. Özellikle veritabanında gecikme süresinin yüksek olduğu durumlarda (örneğin bulut hizmetleri) birden çok ağ gidiş dönüşleri performansı düşürebilir.
- Bazı veritabanları birden çok sorgunun sonuçlarının aynı anda (MARS, Sqlite ile SQL Server) tüketilmesine izin verirken, çoğu belirli bir noktada yalnızca tek bir sorgunun etkin olmasına izin verir. Bu nedenle önceki sorgulardan elde edilen tüm sonuçlar, daha sonraki sorguları yürütmeden önce uygulamanızın belleğinde arabelleğe alınmalıdır ve bu da bellek gereksinimlerini artırır.
- Başvuru gezintilerinin yanı sıra koleksiyon gezintilerini de eklerken, bölünmüş sorguların her biri başvuru gezintilerine katılmaları içerir. Bu, özellikle de çok sayıda referans gezinmeleri varsa performansı düşürebilir. Bu sorunu çözmek istiyorsanız lütfen #29182'yi destekleyin.
Ne yazık ki, tüm senaryolara uyan ilgili varlıkları yüklemek için tek bir strateji yoktur. İhtiyaçlarınıza uygun olanı seçmek için tek ve bölünmüş sorguların avantajlarını ve dezavantajlarını dikkatlice göz önünde bulundurun.