sql >> Databasteknik >  >> RDS >> Sqlserver

Entity-framework-koden är långsam när du använder Include() många gånger

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 och Root.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.



  1. MySQL-prestanda:MyISAM vs InnoDB

  2. Går med till MAX datumpost i grupp

  3. Hur använder man en dynamisk parameter i en IN-klausul i en JPA-namnad fråga?

  4. En titt på Oracle Group-by Bug