Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Кодът на Entity Framework е бавен, когато се използва Include() много пъти

tl;dr Множество Include s взривява набора от SQL резултати. Скоро става по-евтино да се зареждат данни чрез множество извиквания на база данни, вместо да се изпълнява един мега оператор. Опитайте се да намерите най-добрата комбинация от Include и Load изявления.

изглежда, че има намаление на производителността при използване на Include

Това е подценяване! Множество Include s бързо взривява резултата от SQL заявката както по ширина, така и по дължина. Защо е така?

Коефициент на растеж на Include с

(Тази част важи Entity Framework classic, v6 и по-стари)

Да кажем, че имаме

  • основен обект Root
  • родителски обект Root.Parent
  • дъщерни обекти Root.Children1 и Root.Children2
  • изявление LINQ Root.Include("Parent").Include("Children1").Include("Children2")

Това създава SQL израз, който има следната структура:

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

UNION

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

Тези <PseudoColumns> се състоят от изрази като CAST(NULL AS int) AS [C2], и те служат да имат еднакво количество колони във всички UNION -ed заявки. Първата част добавя псевдо колони за Child2 , втората част добавя псевдо колони за Child1 .

Ето какво означава това за размера на SQL резултатния набор:

  • Брой колони в SELECT клаузата е сборът от всички колони в четирите таблици
  • Броят на редовете е сборът от записи във включените дъщерни колекции

Тъй като общият брой точки от данни е columns * rows , всеки допълнителен Include експоненциално увеличава общия брой точки от данни в резултатния набор. Позволете ми да демонстрирам това, като взема Root отново, сега с допълнителен Children3 колекция. Ако всички таблици имат 5 колони и 100 реда, получаваме:

Едно Include (Root + 1 дъщерна колекция):10 колони * 100 реда =1000 точки от данни.
Две Include s (Root + 2 дъщерни колекции):15 колони * 200 реда =3000 точки от данни.
Три Include s (Root + 3 дъщерни колекции):20 колони * 300 реда =6000 точки от данни.

С 12 Includes това би възлизало на 78 000 точки данни!

Обратно, ако получите всички записи за всяка таблица поотделно вместо 12 Includes , имате 13 * 5 * 100 точки от данни:6500, по-малко от 10%!

Сега тези числа са донякъде преувеличени, тъй като много от тези точки от данни ще бъдат null , така че те не допринасят много за действителния размер на набора от резултати, който се изпраща на клиента. Но размерът на заявката и задачата за оптимизатора на заявки със сигурност ще бъдат засегнати отрицателно от увеличаването на броя на Include с.

Баланс

Така че използвайки Includes е деликатен баланс между цената на обажданията към базата данни и обема на данните. Трудно е да се даде някакво правило, но досега можете да си представите, че обемът на данните обикновено бързо надвишава разходите за допълнителни обаждания, ако има повече от ~3 Includes за дъщерни колекции (но доста повече за родителски Includes). , което само разширява набора от резултати).

Алтернатива

Алтернативата на Include е зареждане на данни в отделни заявки:

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

Това зарежда всички необходими данни в кеша на контекста. По време на този процес EF изпълнява оправяне на връзката чрез който автоматично попълва свойствата за навигация (Root.Children и т.н.) от заредени обекти. Крайният резултат е идентичен с израза с Include s, с изключение на една важна разлика:дъщерните колекции не са маркирани като заредени в мениджъра на състоянието на обекта, така че EF ще се опита да задейства мързеливо зареждане, ако имате достъп до тях. Ето защо е важно да изключите мързеливото зареждане.

В действителност ще трябва да разберете коя комбинация от Include и Load твърденията работят най-добре за вас.

Други аспекти, които трябва да имате предвид

Всеки Includes също така увеличава сложността на заявката, така че оптимизаторът на заявки на базата данни ще трябва да полага все повече усилия, за да намери най-добрия план за заявка. В един момент това може вече да не успее. Също така, когато липсват някои жизненоважни индекси (особено на външни ключове), производителността може да пострада чрез добавяне на Include s, дори и с най-добрия план за заявка.

Ядро на Entity Framework

Картезианска експлозия

По някаква причина описаното по-горе поведение, UNIONed заявки, беше изоставено от EF ядро ​​3. Сега изгражда една заявка с обединявания. Когато заявката е с форма на звезда, това води до декартова експлозия (в SQL резултатния набор). Мога да намеря само бележка, обявяваща тази критична промяна, но не казва защо.

Разделени заявки

За да се противопостави на тази декартова експлозия, ядрото на Entity Framework 5 въведе концепцията за разделени заявки, която позволява зареждане на свързани данни в множество заявки. Той предотвратява изграждането на един масивен, умножен SQL набор от резултати. Освен това, поради по-ниската сложност на заявката, тя може да намали времето, необходимо за извличане на данни, дори при множество двупосочни пътувания. Това обаче може да доведе до непоследователни данни, когато се появят едновременни актуализации.

Множество връзки 1:n извън корена на заявката.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL актуализация с row_number()

  2. Създайте база данни на SQL Server с SQLOPS

  3. Отстраняване на проблеми при работа с дата и час в SQL Server

  4. Как работи IIF() в SQL Server

  5. Как да направя съставен ключ със SQL Server Management Studio?