Freigeben über


Leistungsdiagnose

In diesem Abschnitt werden Möglichkeiten zum Erkennen von Leistungsproblemen in Ihrer EF-Anwendung erläutert. Außerdem erfahren Sie, wie Sie sie die Analyse fortsetzen können, sobald ein problematischer Bereich identifiziert wurde, um die Ursache des Problems zu identifizieren. Es ist wichtig, alle Probleme sorgfältig zu diagnostizieren und zu untersuchen, bevor Sie zu voreilige Schlussfolgerungen ziehen. So vermeiden Sie, dass Sie hinsichtlich der Problemursache von bloßen Vermutungen ausgehen.

Identifizieren von langsamen Datenbankbefehlen mithilfe der Protokollierung

Schlussendlich tut EF nichts anderes als Befehle für Ihre Datenbank vorzubereiten und auszuführen. Für relationale Datenbanken bedeutet dies, dass SQL-Anweisungen über die ADO.NET-Datenbank-API ausgeführt werden. Wenn eine bestimmte Abfrage zu viel Zeit in Anspruch nimmt (z. B. weil ein Index fehlt), können Sie dies erkennen, indem Sie Befehlsausführungsprotokolle prüfen und beobachten, wie lange die Ausführung tatsächlich dauert.

EF erleichtert das Erfassen von Ausführungszeiten von Befehlen über einfache Protokollierung oder Microsoft.Extensions.Logging:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;ConnectRetryCount=0")
        .LogTo(Console.WriteLine, LogLevel.Information);
}

Wenn die Protokollierungsebene auf LogLevel.Information festgelegt wird, gibt EF eine Protokollmeldung für jede Befehlsausführung inklusive der benötigten Zeit aus:

info: 06/12/2020 09:12:36.117 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b].[Id], [b].[Name]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'foo'

Der obige Befehl benötigte 4 Millisekunden. Wenn ein bestimmter Befehl mehr Zeit als erwartet benötigt, haben Sie einen möglichen Schuldigen für ein Leistungsproblem gefunden und können sich nun darauf konzentrieren, zu verstehen, warum er langsam ausgeführt wird. Die Befehlsprotokollierung kann auch Fälle anzeigen, in denen unerwartete Datenbankroundtrips auftreten. In diesem Fall würden mehrere Befehle angezeigt werden, obwohl nur ein Befehl zu erwarten wäre.

Warnung

Es ist normalerweise keine gute Idee, die Befehlsausführungsprotokollierung in Ihrer Produktionsumgebung aktiviert zu lassen. Die Protokollierung selbst verlangsamt Ihre Anwendung und kann schnell zur Erstellung umfangreicher Protokolldateien führen, die den Datenträger Ihres Servers füllen. Es wird empfohlen, die Protokollierung nur für kurze Zeit durchzuführen, um Daten zu sammeln – wobei Sie Ihre Anwendung sorgfältig überwachen sollten – oder um Protokollierungsdaten in einem Vorproduktionssystem zu erfassen.

Korrelieren von Datenbankbefehlen mit LINQ-Abfragen

Ein Problem bei der Befehlsausführungsprotokollierung besteht darin, dass es manchmal schwierig ist, SQL-Abfragen und LINQ-Abfragen zu korrelieren: Die von EF ausgeführten SQL-Befehle unterscheiden sich stark von den LINQ-Abfragen, aus denen sie generiert wurden. Um dieser Schwierigkeit zu begegnen, sollten Sie die Abfragetags-Feature von EF verwenden, mit dessen Hilfe Sie einen kleinen, identifizierenden Kommentar in die SQL-Abfrage einfügen können:

var myLocation = new Point(1, 2);
var nearestPeople = (from f in context.People.TagWith("This is my spatial query!")
                     orderby f.Location.Distance(myLocation) descending
                     select f).Take(5).ToList();

Das Tag wird in den Protokollen angezeigt:

-- This is my spatial query!

SELECT TOP(@__p_1) [p].[Id], [p].[Location]
FROM [People] AS [p]
ORDER BY [p].[Location].STDistance(@__myLocation_0) DESC

Es lohnt sich häufig, die wichtigsten Abfragen einer Anwendung auf diese Weise zu taggen, um die Befehlsausführungsprotokolle sofort leichter lesbar zu machen.

Andere Schnittstellen zum Erfassen von Leistungsdaten

Es gibt verschiedene Alternativen zum Protokollierungsfeature von EF zum Erfassen von Befehlsausführungszeiten, die möglicherweise leistungsstärker sind. Datenbanken enthalten in der Regel eigene Tools zur Ablaufverfolgung und Leistungsanalyse, die in der Regel deutlich umfangreichere datenbankspezifische Informationen jenseits einfacher Ausführungszeiten bereitstellen. Die tatsächliche Einrichtung, Funktionen und Nutzung kann sich je nach Datenbank erheblich unterscheiden.

SQL Server Management Studio ein beispielsweise leistungsstarker Client, der eine Verbindung mit Ihrer SQL Server-Instanz herstellen und wertvolle Informationen zu Verwaltung und Leistung bereitstellen kann. Im Rahmen dieses Abschnitts kann hierbei nicht ins Detal gegangen werden, zwei zu erwähnende Funktionen sind jedoch der Aktivitätsmonitor, der ein Live-Dashboard mit Serveraktivitäten (einschließlich der teuersten Abfragen) bereitstellt, sowie das Feature Erweiterte Ereignisse (Extended Events, XEvent), das das Definieren beliebiger Datenerfassungssitzungen ermöglicht, die auf Ihre genauen Anforderungen zugeschnitten werden können. Die SQL Server-Dokumentation zur Überwachung enthält weitere Informationen zu diesen und weiteren Features.

Ein weiterer Ansatz zum Erfassen von Leistungsdaten ist das automatische Sammeln von Informationen, die von EF oder dem Datenbanktreiber über die DiagnosticSource Schnittstelle ausgegeben werden. Diese Daten können dann analysiert oder in einem Dashboard angezeigt werden. Wenn Sie Azure verwenden, bietet Azure Application Insights eine solche sofort einsatzbereite und leistungsstarke Überwachung, die Datenbankleistungs- und Abfrageausführungszeiten bei der Analyse, wie schnell Ihre Webanforderungen bereitgestellt werden, integriert. Weitere Informationen hierzu finden Sie im Tutorial über die Leistung von Application Insights sowie auf der Azure SQL Analytics-Seite.

Überprüfen von Abfrageausführungsplänen

Nachdem Sie eine problematische Abfrage ausfindig gemacht haben, die eine Optimierung erfordert, wird in der Regal im nächsten Schritt der Ausführungsplan der Abfrage analysiert. Wenn Datenbanken eine SQL-Anweisung erhalten, erstellen sie typischerweise einen Ausführungsplan. Dies erfordert manchmal eine komplizierte Entscheidungsfindung, die darauf basiert, welche Indizes definiert wurden, wie viele Daten in Tabellen vorhanden sind usw. (Der Plan selbst sollte normalerweise auf dem Server zwischengespeichert werden, um eine optimale Leistung zu erzielen). Relationale Datenbanken bieten Benutzern in der Regel eine Möglichkeit, den Abfrageplan zusammen mit den berechneten Kosten für verschiedene Teile der Abfrage anzuzeigen. Diese Möglichkeit von unschätzbarem Wert, um Ihre Abfragen zu optimieren.

Informationen zu den ersten Schritten mit SQL Server finden Sie in der Dokumentation zu Abfrageausführungsplänen. Ein typischer Analyseworkflow bestünde aus der Verwendung von SQL Server Management Studio, dem Einfügen des SQL-Codes einer langsamen Abfrage, die über eine der oben genannten Wege identifiziert wurde, und der Erstellung einer grafischen Darstellung des Ausführungsplans:

Anzeigen eines SQL Server-Ausführungsplans

Während Ausführungspläne zunächst kompliziert erscheinen mögen, lohnt es sich, sich mit ihnen vertraut zu machen. Es ist besonders wichtig, die Kosten für jeden Knoten des Plans zu beachten und zu ermitteln, wie Indizes in den verschiedenen Knoten verwendet werden (oder nicht).

Während die oben genannten Informationen spezifisch für SQL Server gelten, bieten andere Datenbanken in der Regel dieselbe Art von Tools mit einer ähnlichen Visualisierung.

Wichtig

Datenbanken generieren je nach tatsächlichen Daten in der Datenbank bisweilen unterschiedliche Abfragepläne. Wenn eine Tabelle z. B. nur wenige Zeilen enthält, kann eine Datenbank entscheiden, für diese Tabelle keinen Index zu verwenden, sondern stattdessen eine vollständige Tabellenscan durchzuführen. Wenn Sie Abfragepläne in einer Testdatenbank analysieren, stellen Sie immer sicher, dass sie Daten enthält, die denen in Ihrem Produktionssystem ähneln.

Metriken

In den obigen Abschnitten wurde erläutert, wie Sie Informationen über Ihre Befehlen abrufen, und wie diese Befehle in der Datenbank ausgeführt werden. Darüber hinaus macht EF eine Reihe von Metriken verfügbar, die weitere Informationen zu den Vorgängen innerhalb von EF selbst sowie der Verwendung durch Ihre Anwendung bereitstellen. Diese Metriken können sehr nützlich sein, um bestimmte Leistungsprobleme und -anomalien zu diagnostizieren, z. B. Probleme beim Zwischenspeichern von Abfragen, die zu einer ständigen Neukompilierung, nicht freigegebenen DbContext-Verlusten und anderen Problemen führen.

Weitere Informationen finden Sie auf der dedizierten Seite zu Metriken in EF.

Benchmarking mit EF Core

Manchmal müssen Sie wissen, ob eine bestimmte Art des Schreibens oder Ausführens einer Abfrage schneller als eine andere ist. Es ist bei dieser Frage wichtig, niemals bloße Vermutungen anzustellen und zu spekulieren, nicht zuletzt, weil es ist sehr einfach ist, ein schnelles Benchmark zusammenzustellen, um eine belastbare Antwort zu erhalten. Beim Schreiben von Benchmarks wird dringend empfohlen, die bekannte BenchmarkDotNet-Bibliothek zu verwenden, die viele Fehlerquellen umgehen hilft, die Benutzer beim Schreiben ihrer eigenen Benchmarks begehen: Haben Sie einige Iterationen als Warm-up durchgeführt? Wie viele Iterationen führt Ihr Benchmark tatsächlich durch? Und warum? Sehen wir uns Benchmarks mithilfe von EF Core genauer an.

Tipp

Das vollständige Benchmark-Projekt für die nachstehende Quelle ist hier verfügbar. Wir ermutigen Sie, es zu kopieren und als Vorlage für Ihre eigenen Benchmarks zu verwenden.

Als einfaches Benchmark-Szenario vergleichen wir die folgenden verschiedenen Methoden zum Berechnen der durchschnittlichen Rangfolge aller Blogs in unserer Datenbank:

  • Laden Sie alle Entitäten, summieren Sie ihre einzelnen Rangfolgen, und berechnen Sie den Mittelwert.
  • Wiederholen Sie den Vorgang, verwenden Sie dabei allerdings eine Abfrage ohne Nachverfolgung. Diese sollte schneller sein, da keine Identitätsauflösung ausgeführt und keine Momentaufnahme der Entitäten zum Zweck der Änderungsnachverfolgung gemacht wird.
  • Vermeiden Sie das Laden der gesamten Blog-Entitätsinstanzen, indem Sie nur die Rangfolge projizieren. Dies erspart uns das Übertragen der nicht benötigten Spalten des Entitätstyps „Blog“.
  • Berechnen Sie den Mittelwert in der Datenbank, indem Sie ihn zu einem Teil der Abfrage machen. Dies sollte die schnellste Methode sein, da alles in der Datenbank berechnet und nur das Ergebnis an den Client zurückübertragen wird.

Mit BenchmarkDotNet schreiben Sie den Code, der als einfache Methode – genau wie ein Komponententest – gebenchmarkt werden soll. BenchmarkDotNet führt alle Methoden automatisch für eine ausreichende Anzahl von Iterationen aus, um die Dauer und den zugewiesenen Arbeitsspeicher zu verlässig zu messen. Nachfolgend finden Sie die verschiedenen Methoden (der vollständige Benchmark-Code kann hier eingesehen werden):

[Benchmark]
public double LoadEntities()
{
    var sum = 0;
    var count = 0;
    using var ctx = new BloggingContext();
    foreach (var blog in ctx.Blogs)
    {
        sum += blog.Rating;
        count++;
    }

    return (double)sum / count;
}

Unten finden Sie Ergebnisse, wie sie von BenchmarkDotNet ausgegeben werden:

Methode Mittelwert Fehler StdDev Median Gewinnanteil RatioSD Gen 0 Gen1 Gen2 Zugeordnet
LoadEntities 2,860,4 us 54,31 us 93,68 us 2,844.5 us 4.55 0,33 210.9375 70.3125 - 1309.56 KB
LoadEntitiesNoTracking 1,353.0 us 21.26 us 18.85 us 1,355.6 us 2,10 0.14 87.8906 3.9063 - 540.09 KB
ProjectOnlyRanking 910.9 us 20.91 us 61.65 us 892.9 us 1,46 0.14 41.0156 0.9766 - 252.08 KB
CalculateInDatabase 627.1 us 14.58 us 42.54 us 626.4 us 1.00 0,00 4.8828 - - 33.27 KB

Hinweis

Da die Methoden den Kontext innerhalb der Methode instanziieren und verwerfen, werden diese Vorgänge für das Benchmark gezählt, obwohl sie streng genommen nicht Teil des Abfragevorgangs sind. Dies sollte aber keine Rolle spielen, wenn das Ziel darin besteht, zwei Alternativen miteinander zu vergleichen (da die Instanziierung und das Verwerfen des Kontexts identisch sind), und außerdem eine ganzheitlichere Messung für den gesamten Betrieb liefern.

Eine Einschränkung von BenchmarkDotNet besteht darin, dass das Tool die einfache Einzelthread-Leistung der von Ihnen bereitgestellten Methoden misst und daher nicht gut für das Benchmarking von Szenarien mit Parallelität geeignet ist.

Wichtig

Stellen Sie immer sicher, dass beim Benchmarking Daten in Ihrer Datenbank vorhanden sind, die den Produktionsdaten ähneln. Andernfalls entsprechen die Benchmarkergebnisse möglicherweise nicht der tatsächlichen Leistung in der Produktion.