Ist Entity Framework 4 eine gute Lösung für eine öffentliche Website mit potenziell 1000 Treffern / Sekunde?
Meines Erachtens ist EF eine praktikable Lösung für meist kleinere oder Intranet-Websites, lässt sich jedoch nicht ohne Weiteres für so etwas wie eine beliebte Community-Website skalieren (ich weiß, dass SO LINQ to SQL verwendet, aber ich hätte gerne mehr Beispiele / Beweise). ..)
Jetzt stehe ich am Scheideweg zwischen einem reinen ADO.NET-Ansatz und EF4. Denken Sie, dass die verbesserte Entwicklerproduktivität mit EF die verlorene Leistung und den granularen Zugriff von ADO.NET (mit gespeicherten Prozeduren) wert ist? Gibt es ernsthafte Probleme, mit denen eine Website mit hohem Datenverkehr konfrontiert sein könnte? Wurde EF verwendet?
Danke im Voraus.
quelle
Antworten:
Es hängt ein wenig davon ab, wie viel Abstraktion Sie benötigen . Alles ist ein Kompromiss; zum Beispiel, EF und NHibernate einführen große Flexibilität , die Daten in interessanten und exotischen Modellen für die Darstellung - aber als Folge sie tun Aufwand hinzufügen. Auffälliger Overhead.
Wenn Sie nicht brauchen , um zwischen Datenbankanbieter wechseln kann, und andere pro-Client - Tabellenlayout, und wenn Sie Ihre Daten in erster Linie sind zu lesen , und wenn Sie nicht brauchen , das gleiche Modell in EF verwenden zu können, SSRS , ADO.NET Data Services, usw. - Wenn Sie die absolute Leistung als Schlüsselmaßstab verwenden möchten, können Sie weitaus schlechtere Ergebnisse erzielen als mit dapper . In unseren Tests, die sowohl auf LINQ-to-SQL als auch auf EF basieren, stellen wir fest, dass EF in Bezug auf die Raw- Leseleistung erheblich langsamer ist, vermutlich aufgrund der Abstraktionsschichten (zwischen Speichermodell usw.) und der Materialisierung.
Hier bei SO sind wir besessen-zwanghaft in Bezug auf die rohe Leistung und freuen uns, den Entwicklungshit zu tragen, etwas Abstraktion zu verlieren, um an Geschwindigkeit zu gewinnen. Daher ist unser primäres Tool zum Abfragen der Datenbank " dapper" . Dies ermöglicht es uns sogar, unser bereits vorhandenes LINQ-to-SQL-Modell zu verwenden, aber einfach: Es ist viel schneller. In Leistungstests entspricht dies im Wesentlichen genau der Leistung des manuellen Schreibens des gesamten ADO.NET-Codes (Parameter, Datenlesegeräte usw.), jedoch ohne das Risiko, dass ein Spaltenname falsch ist. Es ist jedoch SQL-basiert (obwohl es gerne SPROCs verwendet, wenn dies das Gift Ihrer Wahl ist). Dies hat den Vorteil, dass keine zusätzliche Verarbeitung erforderlich ist , sondern ein System für Leute, die SQL mögen. Was ich denke: keine schlechte Sache!
Eine typische Abfrage könnte beispielsweise sein:
Das ist praktisch, spritzensicher usw. - aber ohne jede Menge Datenlesegeräte. Beachten Sie, dass es zwar sowohl horizontale als auch vertikale Partitionen zum Laden komplexer Strukturen verarbeiten kann, jedoch kein verzögertes Laden unterstützt (aber: Wir sind große Fans des expliziten Ladens - weniger Überraschungen).
In dieser Antwort möchte ich nicht erwähnen, dass EF nicht für großvolumige Arbeiten geeignet ist. einfach: ich weiß, dass dapper es drauf ankommt.
quelle
Die Frage "Welches ORM soll ich verwenden?" Zielt wirklich auf die Spitze eines riesigen Eisbergs ab, wenn es um die allgemeine Datenzugriffsstrategie und die Leistungsoptimierung in einer groß angelegten Anwendung geht.
Alle folgenden Dinge ( ungefähr in der Reihenfolge ihrer Wichtigkeit) werden den Durchsatz beeinflussen, und alle werden von den meisten wichtigen ORM-Frameworks (manchmal auf unterschiedliche Weise) behandelt:
Datenbankdesign und -pflege
Dies ist mit großem Abstand die wichtigste Determinante für den Durchsatz einer datengesteuerten Anwendung oder Website und wird von Programmierern häufig völlig ignoriert.
Wenn Sie nicht die richtigen Normalisierungstechniken verwenden, ist Ihre Website zum Scheitern verurteilt. Wenn Sie keine Primärschlüssel haben, ist fast jede Abfrage hundeschwach. Wenn Sie bekannte Anti-Patterns verwenden, z. B. Tabellen für Schlüssel-Wert-Paare (AKA Entity-Attribute-Value), explodieren Sie die Anzahl der physischen Lese- und Schreibvorgänge.
Wenn Sie die Funktionen, die Ihnen die Datenbank bietet, wie Seitenkomprimierung,
FILESTREAM
Speicher (für Binärdaten),SPARSE
Spalten,hierarchyid
Hierarchien usw. (alle SQL Server-Beispiele) nicht nutzen, werden Sie nicht in der Nähe von sehen Leistung, die Sie sehen könnten .Sie sollten sich über Ihre Datenzugriffsstrategie Gedanken machen, nachdem Sie Ihre Datenbank entworfen und sich davon überzeugt haben, dass sie zumindest vorerst so gut wie möglich ist.
Eifriges vs. faules Laden
Die meisten ORMs verwendeten eine Technik, die als verzögertes Laden von Beziehungen bezeichnet wird. Dies bedeutet, dass standardmäßig jeweils eine Entität (Tabellenzeile) geladen wird und jedes Mal, wenn eine oder mehrere verwandte (fremde) Entitäten geladen werden müssen, ein Roundtrip zur Datenbank durchgeführt wird Schlüssel) Zeilen.
Dies ist keine gute oder schlechte Sache, sondern hängt vielmehr davon ab, was mit den Daten tatsächlich gemacht wird und wie viel Sie im Vorfeld wissen. Manchmal ist Lazy-Loading genau das Richtige. Beispielsweise kann NHibernate entscheiden, überhaupt nichts abzufragen und einfach einen Proxy für eine bestimmte ID zu generieren . Wenn Sie nur die ID selbst benötigen, warum sollten Sie danach fragen? Wenn Sie dagegen versuchen, einen Baum jedes einzelnen Elements in einer dreistufigen Hierarchie zu drucken, wird das verzögerte Laden zu einer O (N²) -Operation, die für die Leistung äußerst schlecht ist.
Ein interessanter Vorteil der Verwendung von "reinem SQL" (dh ADO.NET-Abfragen / gespeicherten Prozeduren) besteht darin, dass Sie gezwungen sind, genau zu überlegen, welche Daten für die Anzeige eines bestimmten Bildschirms oder einer bestimmten Seite erforderlich sind. ORMs und Funktionen zum verzögerten Laden hindern Sie nicht daran , aber sie bieten Ihnen die Möglichkeit, ... faul zu sein und die Anzahl der von Ihnen ausgeführten Abfragen aus Versehen zu explodieren. Sie müssen also die Funktionen Ihres ORM verstehen, die Sie benötigen, und die Anzahl der Abfragen, die Sie für eine bestimmte Seitenanforderung an den Server senden, stets im Auge behalten.
Caching
Alle wichtigen ORMs verfügen über einen Cache der ersten Ebene, den "Identitätscache" der AKA. Wenn Sie also dieselbe Entität zweimal anhand ihrer ID anfordern, ist kein zweiter Roundtrip erforderlich ) gibt Ihnen die Möglichkeit, optimistische Parallelität zu verwenden.
Der L1-Cache ist in L2S und EF ziemlich undurchsichtig. Man muss sich darauf verlassen, dass er funktioniert. NHibernate geht expliziter vor (
Get
/Load
vs.Query
/QueryOver
). Solange Sie versuchen, nach ID zu fragen, sollten Sie hier in Ordnung sein. Viele Leute vergessen den L1-Cache und suchen immer wieder nach derselben Entität, die nicht ihrer ID entspricht (dh nach einem Suchfeld). Wenn Sie dies tun müssen, sollten Sie die ID oder sogar die gesamte Entität für zukünftige Suchvorgänge speichern.Es gibt auch einen Level 2 Cache ("Query Cache"). NHibernate hat dies eingebaut. Linq to SQL und Entity Framework haben Abfragen kompiliert , wodurch die Auslastung des Anwendungsservers erheblich reduziert werden kann, indem der Abfrageausdruck selbst kompiliert wird, die Daten jedoch nicht zwischengespeichert werden. Microsoft scheint dies eher als Anwendungsproblem als als als Problem des Datenzugriffs zu betrachten, und dies ist eine große Schwachstelle von L2S und EF. Unnötig zu erwähnen, dass dies auch eine Schwachstelle von "rohem" SQL ist. Um mit einem anderen ORM als NHibernate eine wirklich gute Leistung zu erzielen, müssen Sie Ihre eigene Caching-Fassade implementieren.
Es gibt auch eine L2-Cache- "Erweiterung" für EF4, die in Ordnung ist , aber keinen wirklichen Ersatz für einen Cache auf Anwendungsebene darstellt.
Anzahl der Abfragen
Relationale Datenbanken basieren auf Datensätzen . Sie sind wirklich gut auf der Herstellung große Datenmengen in kürzester Zeit, aber sie sind bei weitem nicht so gut in Bezug auf Abfrage Latenz , weil es eine bestimmte Menge an Overhead in jedem Befehl beteiligt. Eine gut gestaltete App sollte die Stärken dieses DBMS nutzen und versuchen, die Anzahl der Abfragen zu minimieren und die Datenmenge in jedem zu maximieren.
Jetzt sage ich nicht, die gesamte Datenbank abzufragen, wenn Sie nur eine Zeile benötigen. Was ich sagen will ist, wenn Sie die Notwendigkeit
Customer
,Address
,Phone
,CreditCard
, undOrder
Reihen alle zur gleichen Zeit , um eine einzelne Seite zu dienen, dann sollten Sie fragen , für sie alle zur gleichen Zeit, nicht ausführen jeweils getrennt Abfrage. Manchmal ist es schlimmer als das. Sie werden sehen, dass CodeCustomer
5 Mal hintereinander denselben Datensatz abfragt , um zuerst denId
, dann denName
, dann denEmailAddress
, dann ... zu erhalten. Es ist lächerlich ineffizient.Selbst wenn Sie mehrere Abfragen ausführen müssen, die alle mit völlig unterschiedlichen Datensätzen arbeiten, ist es in der Regel immer noch effizienter, alle Abfragen als einzelnes "Skript" an die Datenbank zu senden und mehrere Ergebnismengen zurückzugeben. Es ist der Aufwand, um den Sie sich kümmern, nicht die Gesamtmenge der Daten.
Das mag sich nach gesundem Menschenverstand anhören, aber es ist oft sehr einfach, den Überblick über alle Abfragen zu verlieren, die in verschiedenen Teilen der Anwendung ausgeführt werden. Ihr Mitgliedschaftsanbieter fragt die Benutzer- / Rollentabellen ab, Ihre Header-Aktion fragt den Einkaufswagen ab, Ihre Menü-Aktion fragt die Site-Map-Tabelle ab, Ihre Sidebar-Aktion fragt die vorgestellte Produktliste ab und dann ist Ihre Seite möglicherweise in einige separate autonome Bereiche unterteilt, die Fragen Sie die Tabellen "Bestellverlauf", "Zuletzt angezeigt", "Kategorie" und "Inventar" separat ab. Bevor Sie dies wissen, führen Sie 20 Abfragen aus, bevor Sie überhaupt mit der Bereitstellung der Seite beginnen können. Es zerstört einfach die Leistung.
Einige Frameworks - und ich denke hier hauptsächlich an NHibernate - sind unglaublich schlau und ermöglichen es Ihnen, so genannte Futures zu verwenden, die ganze Abfragen stapeln und versuchen, sie alle auf einmal in der letzten Minute auszuführen. AFAIK, Sie sind auf sich allein gestellt, wenn Sie dies mit einer der Microsoft-Technologien tun möchten. Sie müssen es in Ihre Anwendungslogik integrieren.
Indizierung, Prädikate und Projektionen
Mindestens 50% der Entwickler, mit denen ich spreche, und sogar einige Datenbankadministratoren scheinen Probleme mit dem Konzept der Indexabdeckung zu haben. Sie denken, "nun, die
Customer.Name
Spalte ist indiziert, also sollte jede Suche, die ich nach dem Namen mache, schnell sein." Dies funktioniert jedoch nur, wenn derName
Index die bestimmte Spalte abdeckt, nach der Sie suchen. In SQL Server ist diesINCLUDE
in derCREATE INDEX
Anweisung erledigt .Wenn Sie
SELECT *
überall naiv verwenden - und dies ist mehr oder weniger das, was jeder ORM tun wird, sofern Sie nicht ausdrücklich etwas anderes mit einer Projektion angeben -, kann das DBMS Ihre Indizes möglicherweise vollständig ignorieren, da sie nicht abgedeckte Spalten enthalten. Eine Projektion bedeutet zum Beispiel, dass stattdessen:Sie tun dies stattdessen:
Und dies wird für die meisten modernen ORMs, weisen sie nur zu gehen und fragen Sie die
Id
undName
Spalten , die vermutlich durch den Index abgedeckt sind (aber nicht dasEmail
,LastActivityDate
oder was auch immer andere Spalten , die Sie dort bleiben passiert).Es ist auch sehr einfach, Indexvorteile durch die Verwendung unangemessener Prädikate vollständig zunichte zu machen. Zum Beispiel:
... sieht fast identisch mit unserer vorherigen Abfrage aus, führt jedoch zu einem vollständigen Tabellen- oder Index-Scan, da er in übersetzt wird
LIKE '%Doe%'
. Ähnlich ist eine andere Abfrage, die verdächtig einfach aussieht:Vorausgesetzt, Sie haben einen Index
BirthDate
, hat dieses Prädikat eine gute Chance, ihn völlig unbrauchbar zu machen. Unser hypothetischer Programmierer hier hat offensichtlich versucht, eine Art dynamische Abfrage zu erstellen ("filtern Sie das Geburtsdatum nur, wenn dieser Parameter angegeben wurde"), aber dies ist nicht der richtige Weg, dies zu tun. Stattdessen so geschrieben:... jetzt kann die DB-Engine dies parametrieren und eine Indexsuche durchführen. Eine geringfügige, scheinbar unbedeutende Änderung des Abfrageausdrucks kann die Leistung drastisch beeinträchtigen.
Leider macht es LINQ im Allgemeinen allzu einfach, schlechte Abfragen wie diese zu schreiben, da die Anbieter manchmal raten können, was Sie versucht haben, und die Abfrage optimieren können, und manchmal nicht. Am Ende stehen Ihnen frustrierend inkonsistente Ergebnisse zur Verfügung, die für einen erfahrenen Datenbankadministrator (jedenfalls) offensichtlich gewesen wären, wenn Sie nur einfaches altes SQL geschrieben hätten.
Grundsätzlich kommt es darauf an, dass Sie sowohl das generierte SQL als auch die Ausführungspläne, zu denen es führt, genau im Auge behalten müssen. Wenn Sie nicht die erwarteten Ergebnisse erzielen, haben Sie keine Angst, das zu umgehen Von Zeit zu Zeit eine ORM-Schicht erstellen und die SQL von Hand codieren. Dies gilt für jedes ORM, nicht nur für EF.
Transaktionen und Sperren
Müssen Sie Daten anzeigen, die bis zur Millisekunde aktuell sind? Vielleicht - es kommt darauf an - aber wahrscheinlich nicht. Leider bietet Ihnen Entity Framework keine Funktionen
nolock
, die Sie nurREAD UNCOMMITTED
auf Transaktionsebene (nicht auf Tabellenebene) verwenden können. Tatsächlich ist keiner der ORMs in dieser Hinsicht besonders zuverlässig. Wenn Sie Dirty Reads durchführen möchten, müssen Sie sich auf die SQL-Ebene begeben und Ad-hoc-Abfragen oder gespeicherte Prozeduren schreiben. Es kommt also darauf an, wie einfach es für Sie ist, dies im Rahmen zu tun.Entity Framework hat in dieser Hinsicht einen langen Weg zurückgelegt - Version 1 von EF (in .NET 3.5) war fürchterlich, und es war unglaublich schwierig, die "Entities" -Abstraktion zu durchbrechen, aber jetzt haben Sie ExecuteStoreQuery und Translate , also ist es wirklich nicht so schlecht. Schließe Freundschaften mit diesen Jungs, weil du sie häufig verwendest.
Es gibt auch das Problem der Schreibsperren und Deadlocks sowie die allgemeine Praxis, Sperren in der Datenbank so kurz wie möglich zu halten. In dieser Hinsicht sind die meisten ORMs (einschließlich Entity Framework) in der Regel besser als unformatiertes SQL, da sie die Einheit des Arbeitsmusters kapseln , das in EF SaveChanges ist . Mit anderen Worten, Sie können Entitäten nach Herzenslust "einfügen" oder "aktualisieren" oder "löschen". Dabei können Sie sicher sein, dass keine Änderungen tatsächlich in die Datenbank übertragen werden, bis Sie die Arbeitseinheit festschreiben.
Beachten Sie, dass eine UOW nicht mit einer lang laufenden Transaktion vergleichbar ist. Die UOW verwendet weiterhin die optimistischen Parallelitätsfunktionen des ORM und verfolgt alle Änderungen im Speicher . Bis zum endgültigen Festschreiben wird keine einzige DML-Anweisung ausgegeben. Dies hält die Transaktionszeiten so gering wie möglich. Wenn Sie Ihre Anwendung mit Raw SQL erstellen, ist es ziemlich schwierig, dieses verzögerte Verhalten zu erreichen.
Was dies konkret für EF bedeutet: Machen Sie Ihre Arbeitseinheiten so grob wie möglich und legen Sie sie erst fest, wenn Sie es unbedingt müssen. Wenn Sie dies tun, kommt es zu einer viel geringeren Sperrenkonkurrenz als bei der zufälligen Verwendung einzelner ADO.NET-Befehle.
Abschließend:
EF ist für Anwendungen mit hohem Datenverkehr und hoher Leistung vollkommen in Ordnung, genau wie jedes andere Framework für Anwendungen mit hohem Datenverkehr und hoher Leistung. Was zählt, ist, wie Sie es verwenden. Hier ist ein kurzer Vergleich der beliebtesten Frameworks und ihrer Leistungsmerkmale (Legende: N = Nicht unterstützt, P = Teilweise, Y = Ja / Unterstützt):
Wie Sie sehen, schneidet EF4 (die aktuelle Version) nicht allzu schlecht ab, aber es ist wahrscheinlich nicht die beste, wenn die Leistung Ihr Hauptanliegen ist. NHibernate ist in diesem Bereich viel ausgereifter und sogar Linq to SQL bietet einige leistungssteigernde Funktionen, die EF noch nicht bietet. RAW ADO.NET ist für sehr spezielle Datenzugriffsszenarien häufig schneller , aber wenn Sie alle Komponenten zusammenfassen, bietet es nicht wirklich viele wichtige Vorteile, die Sie aus den verschiedenen Frameworks ziehen.
Und um ganz sicherzugehen, dass ich wie ein kaputter Rekord klinge, spielt dies keine Rolle, wenn Sie Ihre Datenbank-, Anwendungs- und Datenzugriffsstrategien nicht richtig entwerfen. Alle Elemente in der obigen Tabelle dienen dazu, die Leistung über die Grundlinie hinaus zu verbessern . Meistens ist die Basislinie selbst diejenige, die am meisten verbessert werden muss.
quelle
Bearbeiten: Basierend auf der großartigen Antwort von @Aaronaught füge ich ein paar Punkte hinzu, die auf die Leistung mit EF abzielen. Diesen neuen Punkten wird das Präfix Bearbeiten vorangestellt.
Die größte Verbesserung der Leistung bei Websites mit hohem Datenaufkommen wird durch Caching (= Vermeiden von Webserververarbeitung oder Datenbankabfragen) und anschließende asynchrone Verarbeitung erzielt, um das Blockieren von Threads bei Datenbankabfragen zu vermeiden.
Es gibt keine kugelsichere Antwort auf Ihre Frage, da dies immer von den Anwendungsanforderungen und der Komplexität der Anfragen abhängt. Die Wahrheit ist, dass die Entwicklerproduktivität mit EF die Komplexität verbirgt, die in vielen Fällen zu einer falschen Verwendung von EF und einer schrecklichen Leistung führt. Die Idee, dass Sie eine abstrahierte Oberfläche auf hoher Ebene für den Datenzugriff verfügbar machen können und dass sie in allen Fällen reibungslos funktioniert, funktioniert nicht. Auch bei ORM müssen Sie wissen, was sich hinter der Abstraktion verbirgt und wie Sie sie richtig einsetzen.
Wenn Sie noch keine Erfahrung mit EF haben, werden Sie beim Umgang mit Leistung vielen Herausforderungen begegnen. Im Vergleich zu ADO.NET können Sie bei der Arbeit mit EF viel mehr Fehler machen. Außerdem wird in EF eine Menge zusätzlicher Verarbeitungsschritte ausgeführt, sodass EF immer erheblich langsamer als natives ADO.NET ist. Dies können Sie anhand einer einfachen Proof-of-Concept-Anwendung messen.
Wenn Sie die beste Leistung von EF erzielen möchten, müssen Sie höchstwahrscheinlich:
MergeOption.NoTracking
SqlCommand
Befehle verwenden, die mehrere Einfügungen, Aktualisierungen oder Löschvorgänge enthalten. Mit EF wird jedoch jeder dieser Befehle in einem separaten Roundtrip zur Datenbank ausgeführt.GetByKey
in der ObjectContext-API oderFind
in der DbContext-API), um zuerst den Cache abzufragen. Wenn Sie Linq-to-Entities oder ESQL verwenden, wird ein Roundtrip zur Datenbank erstellt und anschließend eine vorhandene Instanz aus dem Cache zurückgegeben.Ich bin nicht sicher, ob SO noch L2S verwendet. Sie entwickelten neues Open Source ORM namens Dapper und ich denke, der Hauptgrund für diese Entwicklung war die Leistungssteigerung.
quelle