tl;dr Flera Include
s spränga SQL-resultatuppsättningen. Snart blir det billigare att ladda data genom flera databasanrop istället för att köra en megasats. Försök att hitta den bästa blandningen av Include
och Load
uttalanden.
det verkar som att det finns en prestationsstraff när du använder Inkludera
Det är en underdrift! Flera Include
s spränger snabbt SQL-frågans resultat både i bredd och längd. Varför är det så?
Tillväxtfaktor för Include
s
(Denna del gäller Entity Framework classic, v6 och tidigare)
Låt oss säga att vi har
- rotentitet
Root
- överordnad enhet
Root.Parent
- underordnade enheter
Root.Children1
ochRoot.Children2
- en LINQ-sats
Root.Include("Parent").Include("Children1").Include("Children2")
Detta bygger en SQL-sats som har följande struktur:
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2
Dessa <PseudoColumns>
består av uttryck som CAST(NULL AS int) AS [C2],
och de tjänar till att ha samma antal kolumner i alla UNION
-ed frågor. Den första delen lägger till pseudokolumner för Child2
, den andra delen lägger till pseudokolumner för Child1
.
Detta är vad det betyder för storleken på SQL-resultatuppsättningen:
- Antal kolumner i
SELECT
sats är summan av alla kolumner i de fyra tabellerna - Antalet rader är summan av poster i inkluderade underordnade samlingar
Eftersom det totala antalet datapunkter är columns * rows
, varje ytterligare Include
ökar exponentiellt det totala antalet datapunkter i resultatuppsättningen. Låt mig visa det genom att ta Root
igen, nu med ytterligare en Children3
samling. Om alla tabeller har 5 kolumner och 100 rader får vi:
En Include
(Root
+ 1 barnsamling):10 kolumner * 100 rader =1 000 datapunkter.
Två Include
s (Root
+ 2 underordnade samlingar):15 kolumner * 200 rader =3 000 datapunkter.
Tre Include
s (Root
+ 3 underordnade samlingar):20 kolumner * 300 rader =6000 datapunkter.
Med 12 Includes
detta skulle uppgå till 78 000 datapunkter!
Omvänt, om du får alla poster för varje tabell separat istället för 12 Includes
, du har 13 * 5 * 100
datapunkter:6500, mindre än 10 %!
Nu är dessa siffror något överdrivna eftersom många av dessa datapunkter kommer att vara null
, så de bidrar inte mycket till den faktiska storleken på resultatuppsättningen som skickas till klienten. Men frågestorleken och uppgiften för frågeoptimeraren påverkas säkert negativt av ökande antal Include
s.
Saldo
Så att använda Includes
är en känslig balans mellan kostnaden för databassamtal och datamängden. Det är svårt att ge en tumregel, men vid det här laget kan du föreställa dig att datavolymen i allmänhet snabbt växer ur kostnaden för extra samtal om det finns mer än ~3 Includes
för underordnade samlingar (men en hel del mer för förälder Includes
, som bara breddar resultatuppsättningen).
Alternativ
Alternativet till Include
är att ladda data i separata frågor:
context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);
Detta laddar all nödvändig data till kontextens cache. Under denna process kör EF relationsfixup genom vilken den automatiskt fyller i navigeringsegenskaper (Root.Children
etc.) av laddade enheter. Slutresultatet är identiskt med satsen med Include
s, förutom en viktig skillnad:de underordnade samlingarna är inte markerade som inlästa i entity state manager, så EF kommer att försöka utlösa lazy loading om du kommer åt dem. Det är därför det är viktigt att stänga av lazy loading.
I verkligheten måste du ta reda på vilken kombination av Include
och Load
uttalanden fungerar bäst för dig.
Andra aspekter att överväga
Varje Include
ökar också frågekomplexiteten, så databasens frågeoptimerare måste anstränga sig allt mer för att hitta den bästa frågeplanen. Någon gång kanske detta inte längre lyckas. Dessutom, när vissa viktiga index saknas (särskilt på främmande nycklar) kan prestanda försämras genom att lägga till Include
s, även med den bästa frågeplanen.
Entity Framework core
Kartesisk explosion
Av någon anledning övergavs beteendet som beskrivs ovan, UNIONed queries, från och med EF core 3. Den bygger nu en fråga med joins. När frågan är "stjärnformad" leder detta till kartesisk explosion (i SQL-resultatuppsättningen). Jag kan bara hitta ett meddelande som tillkännager denna brytande förändring, men det står inte varför.
Dela frågor
För att motverka denna kartesiska explosion introducerade Entity Framework core 5 konceptet med delade frågor som gör det möjligt att ladda relaterade data i flera frågor. Det förhindrar att bygga en massiv, multiplicerad SQL-resultatuppsättning. På grund av lägre frågekomplexitet kan det också minska tiden det tar att hämta data även med flera rundresor. Det kan dock leda till inkonsekventa data när samtidiga uppdateringar sker.
Flera 1:n-relationer utanför frågeroten.