sql >> Database >  >> RDS >> Sqlserver

Entiteitskadercode is traag wanneer Include() vaak wordt gebruikt

tl;dr Meerdere Include s blaas de SQL-resultatenset op. Al snel wordt het goedkoper om gegevens te laden door meerdere database-aanroepen in plaats van één mega-statement uit te voeren. Probeer de beste combinatie van Include . te vinden en Load verklaringen.

het lijkt erop dat er een prestatiestraf is bij het gebruik van Include

Dat is een understatement! Meerdere Include s blaast snel het SQL-queryresultaat op, zowel in de breedte als in de lengte. Waarom is dat?

Groeifactor van Include s

(Dit deel is van toepassing op Entity Framework classic, v6 en eerder)

Laten we zeggen dat we hebben

  • root entiteit Root
  • bovenliggende entiteit Root.Parent
  • onderliggende entiteiten Root.Children1 en Root.Children2
  • een LINQ-statement Root.Include("Parent").Include("Children1").Include("Children2")

Dit bouwt een SQL-statement op met de volgende structuur:

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

UNION

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

Deze <PseudoColumns> bestaan ​​uit uitdrukkingen zoals CAST(NULL AS int) AS [C2], en ze dienen om hetzelfde aantal kolommen te hebben in alle UNION -ed vragen. Het eerste deel voegt pseudokolommen toe voor Child2 , het tweede deel voegt pseudo-kolommen toe voor Child1 .

Dit is wat het betekent voor de grootte van de SQL-resultatenset:

  • Aantal kolommen in de SELECT clausule is de som van alle kolommen in de vier tabellen
  • Het aantal rijen is de som van records in opgenomen onderliggende collecties

Aangezien het totale aantal gegevenspunten columns * rows . is , elke extra Include neemt het totale aantal gegevenspunten in de resultatenset exponentieel toe. Laat me dat aantonen door Root . te nemen nogmaals, nu met een extra Children3 verzameling. Als alle tabellen 5 kolommen en 100 rijen hebben, krijgen we:

Eén Include (Root + 1 onderliggende verzameling):10 kolommen * 100 rijen =1000 gegevenspunten.
Twee Include s (Root + 2 onderliggende collecties):15 kolommen * 200 rijen =3000 gegevenspunten.
Drie Include s (Root + 3 onderliggende verzamelingen):20 kolommen * 300 rijen =6000 gegevenspunten.

Met 12 Includes dit zou neerkomen op 78000 datapunten!

Omgekeerd, als u alle records voor elke tabel afzonderlijk krijgt in plaats van 12 Includes , je hebt 13 * 5 * 100 datapunten:6500, minder dan 10%!

Nu zijn deze getallen enigszins overdreven omdat veel van deze gegevenspunten null zullen zijn , dus ze dragen niet veel bij aan de werkelijke grootte van de resultatenset die naar de klant wordt verzonden. Maar de querygrootte en de taak voor de query-optimizer worden zeker negatief beïnvloed door het toenemende aantal Include v.

Saldo

Dus gebruik Includes is een delicaat evenwicht tussen de kosten van database-oproepen en datavolume. Het is moeilijk om een ​​vuistregel te geven, maar je kunt je inmiddels voorstellen dat het datavolume over het algemeen snel groter is dan de kosten van extra oproepen als er meer dan ~3 Includes zijn. voor onderliggende collecties (maar nog veel meer voor bovenliggende Includes , die de resultatenset alleen maar verbreden).

Alternatief

Het alternatief voor Include is om gegevens in afzonderlijke zoekopdrachten te laden:

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

Dit laadt alle vereiste gegevens in de cache van de context. Tijdens dit proces voert EF relatieherstel uit waarmee het automatisch navigatie-eigenschappen invult (Root.Children enz.) door geladen entiteiten. Het eindresultaat is identiek aan het statement met Include s, met uitzondering van een belangrijk verschil:de onderliggende collecties zijn niet gemarkeerd als geladen in de entiteitsstatusmanager, dus EF zal proberen lui laden te activeren als je ze opent. Daarom is het belangrijk om lui laden uit te schakelen.

In werkelijkheid zult u moeten uitzoeken welke combinatie van Include en Load uitspraken werken het beste voor u.

Andere aspecten om te overwegen

Elke Include verhoogt ook de complexiteit van query's, zodat de query-optimizer van de database steeds meer moeite zal moeten doen om het beste queryplan te vinden. Op een gegeven moment lukt dit misschien niet meer. Ook als enkele vitale indexen ontbreken (in het bijzonder op externe sleutels), kunnen de prestaties eronder lijden door Include toe te voegen. s, zelfs met het beste zoekplan.

Entity Framework-kern

Cartesiaanse explosie

Om de een of andere reden werd het hierboven beschreven gedrag, UNIONed-query's, opgegeven vanaf EF core 3. Het bouwt nu één query met joins. Wanneer de query de vorm van een "ster" heeft, leidt dit tot een cartesiaanse explosie (in de SQL-resultatenset). Ik kan alleen een briefje vinden waarin deze belangrijke wijziging wordt aangekondigd, maar er staat niet in waarom.

Zoekopdrachten splitsen

Om deze Cartesiaanse explosie tegen te gaan, introduceerde Entity Framework core 5 het concept van gesplitste query's waarmee gerelateerde gegevens in meerdere query's kunnen worden geladen. Het voorkomt het bouwen van één enorme, vermenigvuldigde SQL-resultatenset. Vanwege de lagere querycomplexiteit kan het ook de tijd verminderen die nodig is om gegevens op te halen, zelfs bij meerdere roundtrips. Het kan echter leiden tot inconsistente gegevens wanneer gelijktijdige updates plaatsvinden.

Meerdere 1:n-relaties buiten de query-root.



  1. MariaDB-replicatie implementeren voor hoge beschikbaarheid

  2. MONTHNAME() Voorbeelden – MySQL

  3. Controleer of een tekenreeks een subtekenreeks bevat in SQL Server 2005, met behulp van een opgeslagen procedure

  4. Wat is Spring Integration?