sql >> Databáze >  >> RDS >> Sqlserver

Kód entitního rámce je pomalý při použití Include() mnohokrát

tl;dr Více Include s vyhodit do povětří sadu výsledků SQL. Brzy bude levnější načítat data pomocí více databázových volání namísto spouštění jednoho mega příkazu. Pokuste se najít nejlepší kombinaci Include a Load prohlášení.

zdá se, že při použití Include

dochází k omezení výkonu

To je podcenění! Více Include s rychle vyhodí do vzduchu výsledek SQL dotazu jak na šířku, tak na délku. Proč?

Růstový faktor Include s

(Tato část platí Entity Framework classic, v6 a dřívější)

Řekněme, že máme

  • kořenová entita Root
  • nadřazená entita Root.Parent
  • podřízené entity Root.Children1 a Root.Children2
  • příkaz LINQ Root.Include("Parent").Include("Children1").Include("Children2")

Tím se vytvoří příkaz SQL, který má následující strukturu:

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

UNION

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

Tyto <PseudoColumns> sestávají z výrazů jako CAST(NULL AS int) AS [C2], a slouží ke stejnému počtu sloupců ve všech UNION -ed dotazy. První část přidává pseudo sloupce pro Child2 , druhá část přidává pseudo sloupce pro Child1 .

Toto znamená pro velikost sady výsledků SQL:

  • Počet sloupců v SELECT klauzule je součet všech sloupců ve čtyřech tabulkách
  • Počet řádků je součet záznamů v zahrnutých podřízených kolekcích

Protože celkový počet datových bodů je columns * rows , každé další Include exponenciálně zvyšuje celkový počet datových bodů v sadě výsledků. Dovolte mi to demonstrovat pomocí Root znovu, nyní s dalším Children3 sbírka. Pokud mají všechny tabulky 5 sloupců a 100 řádků, dostaneme:

Jeden Include (Root + 1 podřízená kolekce):10 sloupců * 100 řádků =1000 datových bodů.
Dva Include s (Root + 2 podřízené kolekce):15 sloupců * 200 řádků =3000 datových bodů.
Tři Include s (Root + 3 podřízené kolekce):20 sloupců * 300 řádků =6000 datových bodů.

S 12 Includes to by znamenalo 78 000 datových bodů!

A naopak, pokud získáte všechny záznamy pro každou tabulku samostatně namísto 12 Includes , máte 13 * 5 * 100 datové body:6500, méně než 10 %!

Nyní jsou tato čísla poněkud přehnaná, protože mnoho z těchto datových bodů bude null , takže ke skutečné velikosti sady výsledků, která je odeslána klientovi, příliš nepřispívají. Ale velikost dotazu a úkol pro optimalizátor dotazů jsou jistě negativně ovlivněny zvyšujícím se počtem Include s.

Zůstatek

Takže pomocí Includes je křehká rovnováha mezi cenou databázových volání a objemem dat. Těžko říct nějaké pravidlo, ale nyní si dokážete představit, že objem dat obecně rychle převýší náklady na další hovory, pokud je jich více než ~3 Includes pro podřízené kolekce (ale o něco více pro nadřazené Includes , které pouze rozšiřují sadu výsledků).

Alternativa

Alternativa k Include je načíst data v samostatných dotazech:

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

Tím se načtou všechna požadovaná data do mezipaměti kontextu. Během tohoto procesu EF provede opravu vztahu pomocí kterého automaticky vyplní navigační vlastnosti (Root.Children atd.) načtenými entitami. Konečný výsledek je totožný s příkazem Include s, až na jeden důležitý rozdíl:podřízené kolekce nejsou ve správci stavu entity označeny jako načtené, takže se EF pokusí spustit líné načítání, pokud k nim přistoupíte. Proto je důležité vypnout líné načítání.

Ve skutečnosti budete muset zjistit, jakou kombinaci Include a Load výroky pro vás fungují nejlépe.

Další aspekty ke zvážení

Každý Include také zvyšuje složitost dotazů, takže optimalizátor dotazů databáze bude muset vynaložit stále více úsilí na nalezení nejlepšího plánu dotazů. V určitém okamžiku se to již nemusí podařit. Pokud také chybí některé životně důležité indexy (zejména na cizích klíčích), výkon může utrpět přidáním Include s, dokonce i s nejlepším plánem dotazů.

Jádro Entity Framework

Kartézská exploze

Z nějakého důvodu bylo výše popsané chování, UNIONed dotazy, opuštěno od EF core 3. Nyní vytváří jeden dotaz s připojeními. Když je dotaz ve tvaru "hvězdy", vede to ke karteziánské explozi (v sadě výsledků SQL). Mohu najít pouze poznámku oznamující tuto zásadní změnu, ale neříká proč.

Rozdělit dotazy

Aby se čelilo této karteziánské explozi, zavedlo jádro Entity Framework 5 koncept rozdělených dotazů, které umožňují načítání souvisejících dat ve více dotazech. Zabraňuje vytvoření jedné masivní, znásobené sady výsledků SQL. Kvůli nižší složitosti dotazu může také zkrátit dobu potřebnou k načtení dat i při více zpátečních cestách. Při souběžných aktualizacích to však může vést k nekonzistentním datům.

Více vztahů 1:n mimo kořen dotazu.



  1. Jak opravit databázi MySQL v cPanel

  2. Omezení cizího klíče MySQL, kaskádové mazání

  3. SQL Server - najít n-tý výskyt v řetězci

  4. Jak zřetězit řetězce v MySQL pomocí CONCAT()