Leistungstipps für Datenbankzugriff und Entity Framework

Leistungstipps für Datenbankzugriff und Entity Framework

Einer der häufigsten Fallstricke in einem Entwicklungsprojekt ist es, die Leistung zu vergessen, bis ein Problem auftritt. Ich habe oft gehört, wie Knuth gesagt hat:„Vorzeitige Optimierung ist die Wurzel allen Übels“ – was darauf hindeutet, dass es im Moment noch zu früh ist, über Leistungsoptimierung nachzudenken.

Natürlich werden Performance-Tuning und -Verbesserung aufgeschoben und aufgeschoben und noch mehr aufgeschoben … bis es einen Leistungstest in der Vorproduktion gibt und alles fehlschlägt. (Das ist, wenn Sie Glück haben – zumindest haben Sie es entdeckt, bevor es in die Produktion geht. Meistens wird das Problem dort zuerst entdeckt).

Ich glaube daran, dass es zuerst funktioniert, bevor Sie es schnell zum Laufen bringen – aber in dieser Aussage gibt es eine Implikation, dass „arbeiten“ und „schnell arbeiten“ beides sind notwendig. Es reicht nicht aus, es nur zum Laufen zu bringen. Und Knuth wird aus dem Zusammenhang gerissen zitiert – das vollständige Zitat lautet:„Wir sollten kleine Effizienzgewinne vergessen , sagen etwa 97 % der Zeit:Voreilige Optimierung ist die Wurzel allen Übels. “ (meine Betonung). Das sind kleine Effizienzgewinne , keine großen. Er sagt auch:„In etablierten Engineering-Disziplinen wird eine leicht zu erreichende Verbesserung um 12 % niemals als marginal angesehen, und ich glaube, dass derselbe Standpunkt im Software-Engineering vorherrschen sollte “. 12 %!!

Ich möchte 3 Tipps teilen, die ich verwendet habe, um die Leistung einer .NET-Anwendung mit Entity Framework erheblich zu verbessern. Ich habe oft gehört, dass Leute Entity Framework als langsam kritisieren, aber ich halte mich aus den sinnlosen endlosen religiösen Diskussionen darüber heraus, ob es ist oder nicht. Alles, was ich sagen kann, ist, dass der Leistungsengpass meiner Erfahrung nach nie die Schuld von Entity Framework war – es liegt entweder an einer anderen Stelle oder an der Art und Weise, wie Entity Framework verwendet wurde.

Fehlende Indizes

Das hat nichts mit Entity Framework zu tun – das ist eine Änderung an der Datenbank, nicht am .NET-Code. Entity Framework generiert SQL hinter den Kulissen und sendet es zur Ausführung an die Datenbank, und es hat keine Ahnung, ob dieses SQL einen enorm teuren vollständigen Tabellenscan durchführt oder ob es Indizes geschickt verwendet, um zu verhindern, dass jede Zeile durchsucht werden muss in der Datenbank.

Für mich ist dies die erste Anlaufstelle, wenn jemand sagt, dass eine Anwendung, die auf eine Datenbank zugreift, langsam ist. SQL Server verfügt über einige großartige Tools, die dabei helfen – Sie können SQL Profiler verwenden, um eine Ablaufverfolgungsdatei aller SQL-Abfragen aufzuzeichnen, die eine Datenbank über einen bestimmten Zeitraum treffen, und diese Ablaufverfolgungsdatei dann im Datenbankmodul-Optimierungsratgeber verwenden, um zu identifizieren, welche Indizes dass die Engine denkt, dass sie den größten Unterschied für Ihre Anwendung machen wird.

Ich habe erstaunliche Verbesserungen durch diese Technik gesehen – 97 % Verbesserungen sind keine Seltenheit. Auch hier handelt es sich nicht wirklich um einen Entity Framework-Tipp, aber es lohnt sich, ihn zu überprüfen.

Das „Select N+1“-Problem

Also noch einmal, nicht wirklich ein Entity Framework-Problem … ja, hier taucht ein bisschen ein Thema auf! Das haben viele ORMs gemeinsam.

Grundsätzlich sehe ich das Problem als Nebeneffekt des „Lazy Loading“. Angenommen, Ihre Anwendung fragt eine Datenbank nach Autos ab. Autos werden durch ein „Car“-POCO-Objekt dargestellt, das eine Liste von untergeordneten Objekten des POCO-Typs „Wheel“ enthält.

Aus Ihrer Anwendung könnten Sie per Primärschlüssel nach einem Auto mit dem Kennzeichen „ABC 123“ fragen, was (hoffentlich) ein Objekt als Ergebnis zurückgibt. Dann rufen Sie die Methode „Wheels“ auf, um Informationen über die Räder des Autos zu erhalten.

Wenn Ihre Datenbank logisch normalisiert ist, haben Sie hier wahrscheinlich mindestens zwei Abfragen durchgeführt – die ursprüngliche, um das Auto zu erhalten, und dann eine weitere, um Informationen über die Räder zu erhalten. Wenn Sie dann eine Eigenschaft aus dem „Wheel“-Objekt aufrufen, aus dem die Liste besteht, werden Sie wahrscheinlich eine weitere Datenbankabfrage durchführen, um diese Informationen zu erhalten.

Dies ist tatsächlich ein großer Vorteil von ORMs – Sie als Entwickler müssen keine zusätzliche Arbeit leisten, um Informationen über untergeordnete Objekte zu laden, und die Abfrage erfolgt nur, wenn die Anwendung nach Informationen zu diesem Objekt fragt. Es wird alles von Ihnen abstrahiert und es wird Lazy-Loading genannt.

Lazy Loading ist nichts Falsches oder Böses. Wie jedes Werkzeug hat es einen Platz und es gibt Möglichkeiten, es zu missbrauchen. Wo ich gesehen habe, dass es am meisten missbraucht wird, ist in dem Szenario, in dem ein Entwickler:

  • gibt ein Objekt von einem Entity Framework-Aufruf zurück;
  • schließt die Sitzung (d.h. Verbindung zur Datenbank);
  • sucht das übergeordnete Objekt nach einem untergeordneten Objekt und erhält eine Ausnahme, die besagt, dass die Sitzung geschlossen ist;

Der Entwickler tut dann eines von zwei Dingen:

  • Der Entwickler verschiebt die gesamte Logik in die Methode, in der die Sitzung geöffnet ist, da Lazy Loading alle ihre Probleme behebt. Dies führt zu einem großen Durcheinander von Code. Irgendwann – immer – wird dieser Code kopiert und eingefügt, meist in eine Schleife, was zu Unmengen von Datenbankabfragen führt. Da SQL Server brillant ist, hat es wahrscheinlich alle diese Abfragen in wenigen Sekunden erledigt, und niemand bemerkt es wirklich, bis es in der Produktion bereitgestellt wird und Hunderte von Benutzern versuchen, dies auf einmal zu tun, und die Site zusammenbricht. (Ok, das ist übertrieben – Ihre Leistungstest-Events werden das abfangen. Denn natürlich führen Sie Leistungstests durch, bevor Sie in die Produktion gehen, nicht wahr? Nicht wahr? ?)
  • Der bessere Entwickler erkennt, dass es eine schlechte Idee ist, den gesamten Code in eine Methode zu verschieben, und obwohl Lazy Loading dies ermöglicht, missbraucht er die Technik. Sie lesen ein paar Blogs, entdecken dieses Ding namens Eifer Loading und schreiben Code wie diesen:
var car = (from c in context.Cars.Include("Wheel")
            where c.RegistrationPlate == "ABC 123"
            select c).FirstOrDefault<Car>();

Entity Framework ist intelligent genug, um zu erkennen, was hier vor sich geht – anstatt eine dumme Abfrage in der Car-Tabelle durchzuführen, verbindet es sich mit der Wheel-Tabelle und sendet eine Abfrage, um alles zu erhalten, was für das Auto und die Räder benötigt wird.

Das ist also gut – aber in meiner Karriere hat fast jede Anwendung eine viel komplexere Beziehung zwischen Objekt und Datenbankentitäten als nur ein einfaches Eltern-Kind-Objekt. Dies führt zu viel komplexeren Abfrageketten.

Eine Technik, die ich erfolgreich eingesetzt habe, besteht darin, eine Datenbankansicht zu erstellen, die alles enthält, was für die Geschäftsmethode der Anwendung erforderlich ist. Ich verwende gerne Views, weil ich dadurch eine viel genauere Kontrolle darüber habe, was genau die Joins zwischen Tabellen sind und welche Felder von der Datenbank zurückgegeben werden. Es vereinfacht auch den Entity Framework-Code. Aber der größte Vorteil ist, dass die Ansicht zu einer Schnittstelle – eigentlich zu einem Vertrag – zwischen der Datenbank und dem Code wird. Wenn Sie also einen DB-Experten haben, der Ihnen sagt:„Schauen Sie, Ihre Leistungsprobleme sind auf das Design Ihrer Datenbank zurückzuführen – ich kann das beheben, aber wenn ich das tue, wird es wahrscheinlich Ihre Anwendung kaputt machen.“ “, können Sie antworten:„Nun, wir fragen die Datenbank über eine Ansicht ab. Solange Sie also in der Lage sind, eine Ansicht mit denselben Spalten und derselben Ausgabe zu erstellen, können Sie die Datenbank ändern, ohne dass dies beeinträchtigt wird uns.

Wenn Sie eine Datenbankansicht verwenden, bedeutet dies natürlich, dass Sie Objekte nicht mit Entity Framework aktualisieren können, da eine Ansicht schreibgeschützt ist … was den Zweck der Verwendung eines ORM zunichte macht. Wenn Sie jedoch jemanden haben, der eine Lösung für eine langsame Website verlangt, ist es viel weniger aufdringlich, eine Ansicht zu erstellen und zu indizieren, als die Anwendung neu zu entwickeln.

Hinweis:Ich befürworte dies nicht als Wundermittel – es ist nur eine Technik, die manchmal ihren Platz hat.

AsNoTracking

Dies ist eine Entity Framework-Einstellung. Wenn Sie Ansichten verwenden – oder Sie wissen, dass Ihr Entity Framework-Aufruf die Datenbank nicht aktualisieren muss – können Sie eine zusätzliche Leistungssteigerung erzielen, indem Sie das Schlüsselwort AsNoTracking verwenden.

var cars = context.Cars.AsNoTracking().Where(c => c.Color == "Red");

Dies gibt Ihnen einen Leistungsschub, wenn Sie große Datenmengen zurückgeben, aber weniger bei kleineren Mengen. Ihre Laufleistung kann variieren – aber denken Sie daran, dass Sie den Kontext nicht aktualisieren müssen, um dies zu verwenden.

Zusammenfassung

  • Ignorieren Sie die Weisheit der Newsgroup-Beiträge, die sagen:„Entity Framework ist einfach langsam, nichts, was Sie tun können“;
  • Führen Sie stattdessen den SQL Server-Profiler auf der Datenbank aus und senden Sie die resultierende Ablaufverfolgungsdatei durch den Database Engine Tuning Adviser von SQL Server, um Indizes zu finden, die die langsamsten Abfragen verbessern;
  • Analysieren Sie den Code, um das „Select N+1“-Problem zu identifizieren – eines davon gibt es fast immer irgendwo im Code. Wenn Sie es finden möchten, deaktivieren Sie Lazy Loading und führen Sie Ihre Tests durch.
  • Wenn Sie große Datenmengen in eine schreibgeschützte Liste zurückgeben, prüfen Sie, ob Sie mit AsNoTracking etwas mehr Leistung aus Ihrer Anwendung herausholen können.