sql >> Database teknologi >  >> RDS >> Sqlserver

Entity-framework-koden er langsom, når du bruger Include() mange gange

tl;dr Flere Include s sprænge SQL-resultatsættet. Snart bliver det billigere at indlæse data ved flere databasekald i stedet for at køre en mega-erklæring. Prøv at finde den bedste blanding af Include og Load udsagn.

det ser ud til, at der er en præstationsstraf, når du bruger Include

Det er en underdrivelse! Flere Include s hurtigt sprænger SQL-forespørgselsresultatet både i bredden og i længden. Hvorfor er det det?

Vækstfaktor for Include s

(Denne del gælder Entity Framework classic, v6 og tidligere)

Lad os sige, at vi har

  • rodenhed Root
  • overordnet enhed Root.Parent
  • underordnede enheder Root.Children1 og Root.Children2
  • en LINQ-sætning Root.Include("Parent").Include("Children1").Include("Children2")

Dette bygger en SQL-sætning, der har følgende struktur:

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1

UNION

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2

Disse <PseudoColumns> består af udtryk som CAST(NULL AS int) AS [C2], og de tjener til at have det samme antal kolonner i alle UNION -ed forespørgsler. Den første del tilføjer pseudokolonner for Child2 , den anden del tilføjer pseudokolonner for Child1 .

Dette er, hvad det betyder for størrelsen af ​​SQL-resultatsættet:

  • Antal kolonner i SELECT klausul er summen af ​​alle kolonner i de fire tabeller
  • Antallet af rækker er summen af ​​poster i inkluderede underordnede samlinger

Da det samlede antal datapunkter er columns * rows , hver yderligere Include øger eksponentielt det samlede antal datapunkter i resultatsættet. Lad mig demonstrere det ved at tage Root igen, nu med en ekstra Children3 kollektion. Hvis alle tabeller har 5 kolonner og 100 rækker, får vi:

Én Include (Root + 1 underordnet samling):10 kolonner * 100 rækker =1000 datapunkter.
To Include s (Root + 2 underordnede samlinger):15 kolonner * 200 rækker =3000 datapunkter.
Tre Include s (Root + 3 underordnede samlinger):20 kolonner * 300 rækker =6000 datapunkter.

Med 12 Includes dette ville svare til 78.000 datapunkter!

Omvendt, hvis du får alle poster for hver tabel separat i stedet for 12 Includes , du har 13 * 5 * 100 datapunkter:6500, mindre end 10 %!

Nu er disse tal noget overdrevet, idet mange af disse datapunkter vil være null , så de bidrager ikke meget til den faktiske størrelse af det resultatsæt, der sendes til klienten. Men forespørgselsstørrelsen og opgaven for forespørgselsoptimeringsværktøjet bliver bestemt negativt påvirket af et stigende antal Include s.

Saldo

Så ved at bruge Includes er en hårfin balance mellem omkostningerne ved databasekald og datavolumen. Det er svært at give en tommelfingerregel, men efterhånden kan du forestille dig, at datamængden generelt hurtigt vokser ud af omkostningerne ved ekstra opkald, hvis der er mere end ~3 Includes for underordnede samlinger (men en hel del mere for overordnet Includes). , som kun udvider resultatsættet).

Alternativ

Alternativet til Include er at indlæse data i separate forespørgsler:

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);

Dette indlæser alle nødvendige data i kontekstens cache. Under denne proces udfører EF relationship fixup hvorved den automatisk udfylder navigationsegenskaber (Root.Children osv.) af indlæste enheder. Slutresultatet er identisk med sætningen med Include s, bortset fra en vigtig forskel:underordnede samlinger er ikke markeret som indlæst i entity state manager, så EF vil forsøge at udløse doven indlæsning, hvis du får adgang til dem. Derfor er det vigtigt at slå doven indlæsning fra.

I virkeligheden bliver du nødt til at finde ud af, hvilken kombination af Include og Load udsagn fungerer bedst for dig.

Andre aspekter at overveje

Hver Include øger også forespørgselskompleksiteten, så databasens forespørgselsoptimering bliver nødt til at gøre en stadig større indsats for at finde den bedste forespørgselsplan. På et tidspunkt lykkes det måske ikke længere. Når nogle vitale indekser mangler (især på fremmede nøgler), kan ydeevne lide ved at tilføje Include s, selv med den bedste forespørgselsplan.

Entity Framework kerne

Kartesisk eksplosion

Af en eller anden grund blev adfærden beskrevet ovenfor, UNIONed forespørgsler, opgivet fra EF kerne 3. Den bygger nu en forespørgsel med joins. Når forespørgslen er "stjerne" formet, fører dette til kartesisk eksplosion (i SQL-resultatsættet). Jeg kan kun finde en note, der annoncerer denne brydende ændring, men den siger ikke hvorfor.

Opdelte forespørgsler

For at imødegå denne kartesiske eksplosion introducerede Entity Framework core 5 konceptet med opdelte forespørgsler, der gør det muligt at indlæse relaterede data i flere forespørgsler. Det forhindrer opbygning af et massivt, multipliceret SQL-resultatsæt. På grund af lavere forespørgselskompleksitet kan det også reducere den tid, det tager at hente data, selv med flere rundrejser. Det kan dog føre til inkonsistente data, når der sker samtidige opdateringer.

Flere 1:n-relationer ud af forespørgselsroden.



  1. Automatisk indeksstyring i Azure SQL-database

  2. MySQL root-adgang fra alle værter

  3. Hvordan implementerer man én-til-en, én-til-mange og mange-til-mange relationer, mens man designer tabeller?

  4. Hvordan håndteres to_date undtagelser i en SELECT-sætning for at ignorere disse rækker?