Czy Entity Framework 4 to dobre rozwiązanie dla publicznej witryny internetowej z potencjalnie 1000 odsłonami na sekundę?
W moim rozumieniu EF jest realnym rozwiązaniem dla większości mniejszych lub intranetowych stron internetowych, ale nie dałoby się łatwo skalować dla czegoś takiego jak popularna strona społeczności (wiem, że SO używa LINQ do SQL, ale .. Chciałbym więcej przykładów / dowodów. ..)
Teraz stoję na rozdrożu albo wyboru czystego podejścia ADO.NET, albo EF4. Czy uważasz, że lepsza wydajność programistów dzięki EF jest warta utraconej wydajności i szczegółowego dostępu do ADO.NET (z procedurami przechowywanymi)? Jakieś poważne problemy, z którymi może się spotkać strona o dużym ruchu, czy używała EF?
Z góry dziękuję.
entity-framework
ado.net
niaher
źródło
źródło
Odpowiedzi:
To zależy trochę od tego, ile potrzebujesz abstrakcji . Wszystko jest kompromisem; na przykład, EF i NHibernate wprowadzają dużą elastyczność do reprezentowania danych w ciekawych i egzotycznych modeli - ale w wyniku czego zrobić dodać napowietrznych. Zauważalny narzut.
Jeśli nie musisz mieć możliwości przełączania się między dostawcami baz danych i różnymi układami tabel dla poszczególnych klientów oraz jeśli dane są głównie odczytywane , i jeśli nie musisz mieć możliwości korzystania z tego samego modelu w EF, SSRS , Usługi danych ADO.NET itp. - jeśli więc chcesz mieć absolutną wydajność jako kluczową miarę, możesz zrobić znacznie gorzej niż spojrzeć na eleganckie . W naszych testach opartych zarówno na LINQ-to-SQL, jak i EF, stwierdzamy, że EF jest znacznie wolniejszy pod względem wydajności odczytu surowego, prawdopodobnie z powodu warstw abstrakcji (między modelem pamięci itp.) I materializacji.
Tutaj, w SO, jesteśmy obsesyjno-kompulsywni w kwestii surowej wydajności i cieszymy się, że wykorzystujemy hit rozwojowy polegający na utracie abstrakcji w celu przyspieszenia. Jako takie nasze podstawowe narzędzie do wyszukiwania w bazie danych jest eleganckie . To pozwala nam nawet korzystać z naszego wcześniej istniejącego modelu LINQ-SQL, ale po prostu: jest on o wiele szybszy. W testach wydajnościowych jest to dokładnie taka sama wydajność jak ręczne pisanie całego kodu ADO.NET (parametry, czytniki danych itp.), Ale bez ryzyka błędnej nazwy kolumny. Jest jednak oparty na SQL (chociaż chętnie używa SPROC, jeśli jest to twoja wybrana trucizna). Zaletą jest to, że nie ma żadnego dodatkowego przetwarzania zaangażowany, ale to jest system dla ludzi, którzy lubią SQL. Co uważam: niezła rzecz!
Typowym zapytaniem może być na przykład:
co jest wygodne, bezpieczne dla iniekcji itp. - ale bez mnóstwa lepkich czytników danych. Pamiętaj, że chociaż może obsługiwać zarówno poziome, jak i pionowe partycje, aby załadować złożone struktury, nie będzie obsługiwał leniwego ładowania (ale: jesteśmy wielkimi fanami bardzo wyraźnego ładowania - mniej niespodzianek).
Uwaga w tej odpowiedzi nie twierdzę, że EF nie nadaje się do pracy w dużych ilościach; po prostu: wiem, że szykowny jest do tego.
źródło
Pytanie „którego ORM powinienem użyć” naprawdę dotyczy czubka ogromnej góry lodowej, jeśli chodzi o ogólną strategię dostępu do danych i optymalizację wydajności w aplikacji na dużą skalę.
Wszystkie następujące rzeczy (z grubsza w kolejności ważności) będą miały wpływ na przepustowość, a wszystkie z nich są obsługiwane (czasami na różne sposoby) przez większość głównych platform ORM:
Projektowanie i utrzymanie baz danych
Jest to, z szerokim marginesem, najważniejszy wyznacznik przepustowości aplikacji lub strony internetowej sterowanej danymi i często całkowicie ignorowany przez programistów.
Jeśli nie użyjesz odpowiednich technik normalizacji, Twoja strona jest skazana na niepowodzenie. Jeśli nie masz kluczy podstawowych, prawie każde zapytanie będzie wolne. Jeśli użyjesz dobrze znanych anty-wzorców, takich jak używanie tabel dla par klucz-wartość (AKA Entity-Attribute-Value) bez powodu, rozbijesz liczbę fizycznych odczytów i zapisów.
Jeśli nie skorzystasz z funkcji, które oferuje baza danych, takich jak kompresja strony,
FILESTREAM
pamięć masowa (dane binarne),SPARSE
kolumny,hierarchyid
hierarchie itd. (Wszystkie przykłady SQL Server), nie zobaczysz nigdzie w pobliżu wydajność, którą można zobaczyć.Powinieneś zacząć martwić się strategią dostępu do danych po zaprojektowaniu bazy danych i przekonaniu siebie, że jest ona tak dobra, jak to tylko możliwe, przynajmniej na razie.
Chętni kontra Leniwi Ładowanie
Większość ORM stosowała technikę zwaną leniwym ładowaniem relacji, co oznacza, że domyślnie ładuje jedną jednostkę (wiersz tabeli) na raz i robi objazd do bazy danych za każdym razem, gdy musi załadować jedną lub wiele powiązanych (zagranicznych) klucz) wiersze.
To nie jest dobra ani zła rzecz, raczej zależy to od tego, co faktycznie zrobimy z danymi i od tego, ile wiesz z góry. Czasami leniwe ładowanie jest absolutnie właściwe. Na przykład NHibernate może zdecydować, że w ogóle nie będzie pytać o nic i po prostu wygeneruje serwer proxy dla określonego identyfikatora. Jeśli wszystko, czego kiedykolwiek potrzebujesz, to sam identyfikator, dlaczego miałbyś prosić o więcej? Z drugiej strony, jeśli próbujesz wydrukować drzewo każdego elementu w 3-poziomowej hierarchii, leniwe ładowanie staje się operacją O (N²), co jest bardzo niekorzystne dla wydajności.
Jedną z interesujących korzyści z używania „czystego SQL” (tj. Surowych zapytań / procedur przechowywanych ADO.NET) jest to, że w zasadzie zmusza cię do zastanowienia się, jakie dane są niezbędne do wyświetlenia danego ekranu lub strony. ORMs i cechy leniwy załadunku nie zapobiega cię od robienia tego, ale oni nie dają możliwość bycia ... cóż, ci leniwi i przypadkowego wybuchu liczbę zapytań wykonują. Musisz więc zrozumieć funkcje ładowania ORM i być zawsze czujnym, jeśli chodzi o liczbę zapytań wysyłanych do serwera dla każdego żądania strony.
Buforowanie
Wszystkie główne ORM utrzymują pamięć podręczną pierwszego poziomu, AKA „pamięć podręczną tożsamości”, co oznacza, że jeśli dwukrotnie zażądasz tego samego bytu według jego identyfikatora, nie wymaga to drugiej podróży w obie strony, a także (jeśli poprawnie zaprojektowałeś bazę danych ) daje możliwość korzystania z optymistycznej współbieżności.
Pamięć podręczna L1 jest dość nieprzezroczysta w L2S i EF, musisz w pewien sposób zaufać, że działa. NHibernate mówi o tym bardziej wyraźnie (
Get
/Load
vs.Query
/QueryOver
). Tak długo, jak będziesz próbował zapytać według identyfikatora w jak największym stopniu, powinieneś być w porządku. Wiele osób zapomina o pamięci podręcznej L1 i wielokrotnie wyszukuje ten sam byt w kółko za pomocą czegoś innego niż jego identyfikator (tj. Pole odnośnika). Jeśli musisz to zrobić, powinieneś zapisać identyfikator, a nawet cały byt na przyszłe wyszukiwania.Istnieje również pamięć podręczna poziomu 2 („pamięć podręczna zapytań”). NHibernate ma to wbudowane. Linq do SQL i Entity Framework mają skompilowane zapytania , które mogą pomóc nieco zmniejszyć obciążenia serwera aplikacji, kompilując samo wyrażenie zapytania, ale nie buforuje danych. Wydaje się, że Microsoft uważa to za problem związany z aplikacją, a nie za dostęp do danych, i jest to główny słaby punkt zarówno L2S, jak i EF. Nie trzeba dodawać, że jest to także słaby punkt „surowego” SQL. Aby uzyskać naprawdę dobrą wydajność w zasadzie z dowolnym ORM innym niż NHibernate, musisz wdrożyć własną fasadę buforującą.
Istnieje również „rozszerzenie” pamięci podręcznej L2 dla EF4, co jest w porządku , ale tak naprawdę nie jest hurtowym zamiennikiem pamięci podręcznej na poziomie aplikacji.
Liczba zapytań
Relacyjne bazy danych są oparte na zestawach danych. Są naprawdę dobre w tworzeniu dużych ilości danych w krótkim czasie, ale nie są tak dobre pod względem opóźnienia zapytań, ponieważ każde polecenie wiąże się z pewnym obciążeniem. Dobrze zaprojektowana aplikacja powinna wykorzystać mocne strony tego DBMS i spróbować zminimalizować liczbę zapytań i zmaksymalizować ilość danych w każdym z nich.
Teraz nie mówię, aby przesyłać zapytania do całej bazy danych, gdy potrzebujesz tylko jednego wiersza. Co mówię jest, jeśli potrzebujesz
Customer
,Address
,Phone
,CreditCard
iOrder
wiersze w tym samym czasie w celu odbycia jedną stronę, to należy poprosić o nich wszystkich w tym samym czasie, nie wykonać każde zapytanie osobno. Czasami jest gorzej, zobaczysz kod, który wysyła kwerendę do tego samegoCustomer
rekordu 5 razy z rzędu, najpierw, aby uzyskaćId
, aName
następnieEmailAddress
, a następnie ... to jest absurdalnie nieefektywne.Nawet jeśli musisz wykonać kilka zapytań, które działają na całkowicie różnych zestawach danych, zwykle bardziej wydajne jest przesłanie ich do bazy danych jako pojedynczego „skryptu” i zwrócenie wielu zestawów wyników. Niepokoi Cię ogólny koszt, a nie całkowita ilość danych.
Może to zabrzmieć jak zdrowy rozsądek, ale często bardzo łatwo jest zgubić wszystkie zapytania wykonywane w różnych częściach aplikacji; Twój dostawca członkostwa pyta tabele użytkowników / ról, twoja akcja Nagłówek pyta o koszyk, twoja akcja Menu pyta o tabelę mapy witryny, twoja akcja na pasku bocznym pyta o listę polecanych produktów, a następnie być może twoja strona jest podzielona na kilka odrębnych autonomicznych obszarów, które przeprowadź osobne zapytania do Tabeli Historii zamówień, Ostatnio oglądane, Kategorii i Zapasów, a zanim się zorientujesz, wykonujesz 20 zapytań, zanim zaczniesz obsługiwać stronę. Po prostu całkowicie niszczy wydajność.
Niektóre frameworki - i myślę tu głównie o NHibernate - są niesamowicie sprytne i pozwalają na użycie czegoś takiego jak futures, które dzielą całe zapytania i próbują wykonać je wszystkie naraz, w ostatniej możliwej chwili. AFAIK, jesteś sam, jeśli chcesz to zrobić za pomocą dowolnej technologii Microsoft; musisz wbudować go w logikę aplikacji.
Indeksowanie, predykaty i prognozy
Przynajmniej 50% deweloperów, z którymi rozmawiam, a nawet niektórzy DBA wydają się mieć problem z koncepcją obejmowania indeksów. Myślą: „cóż,
Customer.Name
kolumna jest indeksowana, więc każde wyszukiwanie nazwy powinno być szybkie”. Tyle że to nie działa w ten sposób, chyba żeName
indeks obejmuje konkretną kolumnę, której szukasz. W SQL Server jest to zrobioneINCLUDE
wCREATE INDEX
instrukcji.Jeśli naiwnie używasz
SELECT *
wszędzie - i to mniej więcej to, co zrobi każdy ORM, chyba że wyraźnie określisz inaczej za pomocą projekcji - wtedy DBMS może bardzo dobrze zignorować twoje indeksy, ponieważ zawierają nieobjęte kolumny. Projekcja oznacza na przykład, że zamiast tego:Robisz to zamiast tego:
I będzie to dla większości nowoczesnych ORMs, instruować go tylko iść i kwerendy
Id
iName
kolumn, które są przypuszczalnie objętych indeksem (ale nieEmail
,LastActivityDate
lub jakikolwiek inny kolumny zdarzyło się trzymać tam).Bardzo łatwo jest również całkowicie wyeliminować wszelkie korzyści związane z indeksowaniem przy użyciu nieodpowiednich predykatów. Na przykład:
... wygląda prawie identycznie jak nasze poprzednie zapytanie, ale w rzeczywistości spowoduje pełne skanowanie tabeli lub indeksu, ponieważ się tłumaczy
LIKE '%Doe%'
. Podobnie inne zapytanie, które wygląda podejrzanie prosto, to:Zakładając, że masz indeks
BirthDate
, ten predykat ma dużą szansę, aby uczynić go całkowicie bezużytecznym. Nasz hipotetyczny programista najwyraźniej próbował stworzyć coś w rodzaju dynamicznego zapytania („filtruj datę urodzenia tylko, jeśli określono ten parametr”), ale nie jest to właściwy sposób, aby to zrobić. Zamiast tego napisane w ten sposób:... teraz silnik DB wie, jak to sparametryzować i przeprowadzić wyszukiwanie indeksu. Jedna niewielka, pozornie nieznaczna zmiana w wyrażeniu zapytania może drastycznie wpłynąć na wydajność.
Niestety, LINQ ogólnie sprawia, że pisanie złych zapytań jest zbyt łatwe, ponieważ czasami dostawcy są w stanie odgadnąć, co próbowaliście zrobić, i zoptymalizować zapytanie, a czasem nie. W efekcie powstają frustrująco niespójne wyniki, które byłyby oślepiająco oczywiste (w każdym razie dla doświadczonego DBA), gdybyś właśnie napisał zwykły stary SQL.
Zasadniczo wszystko sprowadza się do tego, że naprawdę musisz uważnie obserwować zarówno wygenerowany SQL, jak i plany wykonania, do których prowadzą, a jeśli nie osiągniesz oczekiwanych rezultatów, nie bój się ominąć Warstwa ORM raz na jakiś czas i ręcznie koduj SQL. Dotyczy to każdej ORM, nie tylko EF.
Transakcje i blokowanie
Czy potrzebujesz wyświetlać aktualne dane do milisekundy? Może - to zależy - ale prawdopodobnie nie. Niestety, Entity Framework nie daje
nolock
, możesz używać tylkoREAD UNCOMMITTED
na poziomie transakcji (nie na poziomie tabeli). W rzeczywistości żaden z ORM nie jest szczególnie wiarygodny w tym zakresie; jeśli chcesz robić brudne odczyty, musisz zejść do poziomu SQL i pisać zapytania ad-hoc lub procedury składowane. Sprowadza się to do tego, jak łatwo jest to zrobić w ramach.Entity Framework przeszedł długą drogę w tym względzie - wersja 1 EF (w .NET 3.5) była okropna, sprawiła, że niezwykle trudno było przebić się przez abstrakcję „bytów”, ale teraz masz ExecuteStoreQuery i Tłumacz , więc to naprawdę nieźle. Zaprzyjaźnij się z tymi facetami, ponieważ będziesz ich często używać.
Istnieje również kwestia blokowania zapisu i zakleszczeń oraz ogólnej praktyki trzymania blokad w bazie danych przez jak najkrótszy czas. Pod tym względem większość ORM (w tym Entity Framework) faktycznie jest lepsza niż surowy SQL, ponieważ zawierają one wzorzec jednostki pracy , którym w EF jest SaveChanges . Innymi słowy, możesz „wstawiać” lub „aktualizować” lub „usuwać” byty w treści swojego serca, kiedy tylko chcesz, mając pewność, że żadne zmiany nie zostaną faktycznie wprowadzone do bazy danych, dopóki nie wykonasz jednostki pracy.
Należy pamiętać, że UOW nie jest analogiczny do długotrwałej transakcji. UOW nadal korzysta z optymistycznych funkcji współbieżności ORM i śledzi wszystkie zmiany w pamięci . Do ostatniego zatwierdzenia nie jest emitowana ani jedna instrukcja DML. Dzięki temu czasy transakcji są jak najniższe. Jeśli zbudujesz aplikację przy użyciu surowego SQL, osiągnięcie tego odroczonego zachowania jest dość trudne.
Co to w szczególności oznacza dla EF: spraw, aby twoje jednostki pracy były jak najgrubsze i nie przydzielaj ich, dopóki nie będziesz absolutnie tego potrzebował. Zrób to, a skończysz z znacznie mniejszą rywalizacją o blokadę niż przy użyciu indywidualnych poleceń ADO.NET w przypadkowych momentach.
Podsumowując:
EF jest całkowicie odpowiedni dla aplikacji o dużym natężeniu ruchu / o wysokiej wydajności, podobnie jak każda inna struktura jest odpowiednia do aplikacji o dużym natężeniu ruchu / o wysokiej wydajności. Liczy się sposób korzystania z niego. Oto szybkie porównanie najpopularniejszych frameworków i ich funkcji pod względem wydajności (legenda: N = nieobsługiwane, P = częściowe, Y = tak / obsługiwane):
Jak widać, EF4 (obecna wersja) nie wypada zbyt źle, ale prawdopodobnie nie jest najlepszy, jeśli wydajność jest twoim głównym zmartwieniem. NHibernate jest znacznie bardziej dojrzały w tym obszarze, a nawet Linq to SQL zapewnia pewne funkcje zwiększające wydajność, których EF jeszcze nie ma. Surowe ADO.NET często będzie szybsze w przypadku bardzo specyficznych scenariuszy dostępu do danych, ale po złożeniu wszystkich elementów tak naprawdę nie oferuje wielu ważnych korzyści, które można uzyskać z różnych platform.
I żeby się upewnić, że brzmię jak zepsuty zapis, nic z tego nie ma znaczenia, jeśli nie zaprojektujesz właściwie bazy danych, aplikacji i strategii dostępu do danych. Wszystkie elementy powyższej tabeli służą poprawie wydajności wykraczającej poza poziom podstawowy; przez większość czasu sama podstawa wymaga największej poprawy.
źródło
Edycja: W oparciu o świetną odpowiedź na @Aaronaught dodaję kilka punktów kierujących na wydajność za pomocą EF. Te nowe punkty są poprzedzone przez Edycja.
Największą poprawę wydajności w witrynach o dużym ruchu uzyskuje się poprzez buforowanie (= przede wszystkim unikanie przetwarzania przez serwer WWW lub zapytania do bazy danych), a następnie przetwarzanie asynchroniczne, aby uniknąć blokowania wątków podczas wykonywania zapytań do bazy danych.
Nie ma żadnej odpowiedzi na pytanie, ponieważ zawsze zależy to od wymagań dotyczących aplikacji i złożoności zapytań. Prawda jest taka, że produktywność programistów z EF ukrywa złożoność, za którą w wielu przypadkach prowadzi się do nieprawidłowego użycia EF i strasznej wydajności. Pomysł, że możesz udostępnić abstrakcyjny interfejs wysokiego poziomu dla dostępu do danych i będzie on płynnie działać we wszystkich przypadkach, nie działa. Nawet z ORM musisz wiedzieć, co dzieje się za abstrakcją i jak prawidłowo z niej korzystać.
Jeśli nie masz wcześniejszego doświadczenia z EF, napotkasz wiele wyzwań związanych z wydajnością. Możesz popełnić znacznie więcej błędów podczas pracy z EF w porównaniu do ADO.NET. Ponadto w EF jest wykonywanych wiele dodatkowych operacji, więc EF zawsze będzie znacznie wolniejszy niż natywny ADO.NET - można to zmierzyć za pomocą prostej aplikacji sprawdzającej koncepcję.
Jeśli chcesz uzyskać najlepszą wydajność z EF, najprawdopodobniej będziesz musiał:
MergeOption.NoTracking
SqlCommand
zawierającego wiele wstawek, aktualizacji lub usunięć, ale z EF każde takie polecenie zostanie wykonane w osobnej rundzie do bazy danych.GetByKey
w ObjectContext API lubFind
DbContext API), aby najpierw wykonać zapytanie do pamięci podręcznej. Jeśli użyjesz Linq-to-podmiotów lub ESQL, utworzy on objazd do bazy danych, a następnie zwróci istniejącą instancję z pamięci podręcznej.Nie jestem pewien, czy SO nadal używa L2S. Opracowali nowy ORM open source o nazwie Dapper i myślę, że głównym celem tego rozwoju było zwiększenie wydajności.
źródło