Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Prestandaproblem med enskilda frågor
När du arbetar mot relationsdatabaser läser EF in relaterade entiteter genom att introducera JOIN i en enda fråga. JON är ganska standard när du använder SQL, men de kan skapa betydande prestandaproblem om de används felaktigt. Den här sidan beskriver dessa prestandaproblem och visar ett alternativt sätt att läsa in relaterade entiteter som kringgår dem.
Kartesisk explosion
Nu ska vi undersöka följande LINQ-fråga och dess översatta SQL-motsvarighet:
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]
I det här exemplet, eftersom både Posts och Contributors är samlingsnavigeringar av Blog - de är på samma nivå - returnerar relationsdatabaser en korsprodukt: varje rad från Posts är kopplad till varje rad från Contributors. Det innebär att om en viss blogg har 10 inlägg och 10 deltagare returnerar databasen 100 rader för den enskilda bloggen. Det här fenomenet , som ibland kallas kartesisk explosion , kan leda till att stora mängder data oavsiktligt överförs till klienten, särskilt som fler JON:er för syskon läggs till i frågan. Detta kan vara ett stort prestandaproblem i databasprogram.
Observera att kartesisk explosion inte inträffar när de två JON:erna inte är på samma nivå:
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]
I den här frågan Comments är en samlingsnavigering av Post, till skillnad från Contributors i föregående fråga, som var en samlingsnavigering av Blog. I det här fallet returneras en enskild rad för varje kommentar som en blogg har (via sina inlägg) och en korsprodukt inträffar inte.
Dataduplicering
JOIN kan skapa en annan typ av prestandaproblem. Nu ska vi undersöka följande fråga, som bara läser in en enda samlingsnavigering:
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]
Om du tittar på de beräknade kolumnerna innehåller varje rad som returneras av den här frågan egenskaper från både tabellerna Blogs och Posts . Det innebär att bloggegenskaperna dupliceras för varje inlägg som bloggen har. Även om detta vanligtvis är normalt och inte orsakar några problem, om Blogs tabellen råkar ha en mycket stor kolumn (t.ex. binära data eller en enorm text), skulle den kolumnen dupliceras och skickas tillbaka till klienten flera gånger. Detta kan avsevärt öka nätverkstrafiken och påverka programmets prestanda negativt.
Om du faktiskt inte behöver den enorma kolumnen är det lätt att helt enkelt inte fråga efter den:
var blogs = await ctx.Blogs
.Select(b => new
{
b.Id,
b.Name,
b.Posts
})
.ToListAsync();
Genom att använda en projektion för att uttryckligen välja vilka kolumner du vill använda kan du utelämna stora kolumner och förbättra prestandan. Observera att detta är en bra idé oavsett dataduplicering, så överväg att göra det även när du inte läser in en samlingsnavigering. Men eftersom det här projicerar bloggen till en anonym typ spåras inte bloggen av EF och ändringar i den kan inte sparas tillbaka som vanligt.
Det är värt att notera att till skillnad från kartesisk explosion är datadupliceringen som orsakas av JOIN vanligtvis inte betydande, eftersom den duplicerade datastorleken är försumbar. Detta är vanligtvis bara något att oroa sig för om du har stora kolumner i huvudtabellen.
Dela upp frågor
För att kringgå prestandaproblemen som beskrivs ovan kan du med EF ange att en viss LINQ-fråga ska delas upp i flera SQL-frågor. I stället för JON genererar delade frågor ytterligare en SQL-fråga för varje inkluderad samlingsnavigering:
using (var context = new BloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSplitQuery()
.ToListAsync();
}
Följande SQL skapas:
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]
Varning
När du använder delade frågor med Skip/Take on EF-versioner före 10 bör du vara särskilt uppmärksam på att göra din frågeordning helt unik. om du inte gör det kan felaktiga data returneras. Om resultaten till exempel endast sorteras efter datum, men det kan finnas flera resultat med samma datum, kan var och en av de delade frågorna få olika resultat från databasen. Beställning efter både datum och ID (eller någon annan unik egenskap eller kombination av egenskaper) gör beställningen helt unik och undviker det här problemet. Observera att relationsdatabaser inte tillämpar någon ordning som standard, inte ens på den primära nyckeln.
Anmärkning
En-till-en-relaterade entiteter läses alltid in via JON i samma fråga, eftersom det inte har någon prestandapåverkan.
Aktivera delade frågor globalt
Du kan också konfigurera delade frågor som standard för programmets kontext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0",
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}
När delade frågor konfigureras som standard är det fortfarande möjligt att konfigurera specifika frågor som ska köras som enskilda frågor:
using (var context = new SplitQueriesBloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSingleQuery()
.ToListAsync();
}
EF Core använder enkelt frågeläge som standard i avsaknad av någon konfiguration. Eftersom det kan orsaka prestandaproblem genererar EF Core en varning när följande villkor uppfylls:
- EF Core identifierar att frågan läser in flera samlingar.
- Användaren har inte konfigurerat frågedelningsläget globalt.
- Användaren har inte använt
AsSingleQuery/AsSplitQueryoperatorn för frågan.
Om du vill inaktivera varningen konfigurerar du frågedelningsläget globalt eller på frågenivå till ett lämpligt värde.
Egenskaper för delade frågor
Även om delad fråga undviker prestandaproblem som är associerade med JOIN och kartesisk explosion, har den också några nackdelar:
- De flesta databaser garanterar datakonsekvens för enskilda frågor, men det finns inga sådana garantier för flera frågor. Om databasen uppdateras samtidigt när du kör dina frågor kanske resulterande data inte är konsekventa. Du kan minimera det genom att omsluta frågorna i en serialiserbar transaktion eller en ögonblicksbildtransaktion, men om du gör det kan det skapa egna prestandaproblem. Mer information finns i databasens dokumentation.
- Varje fråga innebär för närvarande ytterligare en nätverksrunda till databasen. Flera tur och retur-nätverk kan försämra prestanda, särskilt när svarstiden till databasen är hög (till exempel molntjänster).
- Vissa databaser tillåter användning av resultatet av flera frågor samtidigt (SQL Server med MARS, Sqlite), men de flesta tillåter endast att en enskild fråga är aktiv vid en viss tidpunkt. Därför måste alla resultat från tidigare frågor buffras i programmets minne innan du kör senare frågor, vilket leder till ökade minnesbehov.
- När du inkluderar referensnavigeringar och samlingsnavigeringar inkluderar var och en av de delade frågorna kopplingar till referensnavigeringarna. Detta kan försämra prestanda, särskilt om det finns många referensnavigeringar. Lägg till #29182 om det här är något som du vill se åtgärdat.
Tyvärr finns det ingen strategi för inläsning av relaterade entiteter som passar alla scenarier. Överväg noggrant fördelarna och nackdelarna med enkla och delade frågor för att välja den som passar dina behov.