7 Stimmen

Warum verbraucht die gleiche Linq-to-SQL-Abfrage für ein anderes Projekt auf dem Datenbankserver viel mehr CPU-Zeit?

Ich habe ein Legacy .Net 4-Projekt (nennen wir es "A"), das Linq-to-SQL verwendet, um eine Datenbank abzufragen, und ich habe ein weiteres .Net 4-Projekt (nennen wir es "B") mit ähnlichem, aber nicht gleichem Code, das dieselbe Datenbank wie "A" abfragt.

Beide Projekte:

  • sind C#-Projekte {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
  • verwenden dieselben Assemblys (Version v4.0.30319, derselbe Ordner)
    • System.dll
    • System.Data.dll
    • System.Data.Linq.dll

Der automatisch generierte DataContext ist spezifisch für jedes Projekt, wird aber auf dieselbe Weise instanziiert:

  • gleiche Verbindungszeichenfolge unter Verwendung der SQL-Authentifizierung
  • beide DataContext setzen ihren CommandTimeout von Standard auf 60 Sekunden
  • alle anderen Konfigurationsoptionen für den DataContext sind die Standardeinstellungen

Die Art und Weise, wie die Linq-Abfrage konstruiert ist, ist für die Projekte nicht genau gleich, aber die resultierende Linq-Abfrage ist identisch. Das generierte (T-)SQL-SELECT-Statement ist ebenfalls dasselbe! (SQL-Handles auf dem DB-Server überwacht und überprüft)

Der Datenbankserver ist:

  • Microsoft SQL Server Enterprise 2005 x64 (9.00.4035.00)
  • Betriebssystem: Microsoft Server 2003 R2 SP2 x64

Wenn die überwachte CPU-Zeit (auf dem DB-Server) für die Abfrage von Projekt "A" stark anstieg und eine Befehlszeitüberschreitungsausnahme ausgelöst wurde. (System.Data.SqlClient.SqlException: Timeout abgelaufen)

Andererseits wurde die Abfrage von "B" innerhalb von Sekunden ausgeführt (ungefähr 3). Ich konnte das Verhalten reproduzieren, indem ich den Code von "A" mit denselben Parametern erneut aufgerufen habe (keine Änderungen am Code oder der Datenbank). "B" wurde sogar innerhalb von Sekunden ausgeführt, während "A" seine CPU-Zeit erhöhte.

Bereut, nachdem ein Kollege die Indizes neu erstellt hatte, konnte ich das Verhalten nicht mehr reproduzieren. Derselbe Kollege erwähnte, dass die Abfrage "letzten Monat" schnell lief (obwohl sich der Code seit "letztem Monat" nicht geändert hat...).

Ich habe den Code für beide Projekte debuggt - beide DataContext-Instanzen sahen gleich aus. Der SQL-Handle des DB-Serverprozesses enthält denselben SQL-Befehl. Aber "A" warf eine Zeitüberschreitungsausnahme und "B" wurde wiederholt innerhalb von Sekunden ausgeführt!

Warum verbraucht dieselbe Linq-to-SQL-Abfrage beim Projekt "A" auf dem Datenbankserver viel mehr CPU-Zeit als beim Projekt "B"?

Ganz genau: Wenn die Abfrage "langsam" läuft aus bestimmten Gründen - wiederholt - wie kann dann dieselbe Abfrage schneller ausgeführt werden, nur weil sie von einem anderen Linq-to-SQL-Code aufgerufen wird?

Gibt es möglicherweise Nebenwirkungen, von denen ich noch nichts weiß? Gibt es bestimmte Instanzwerte des DataContext, auf die ich zur Laufzeit speziell achten muss?

Übrigens: Der SQL-Befehl - über SSMS - verwendet bei jedem Durchlauf denselben Abfrageplan.

Zur Vollständigkeit habe ich ein Beispiel von verlinkt:

  • den C#-Code-Fragmenten des Projekts "B" (der Teil SqlRequest.GetQuery sieht für beide Projekte gleich aus)
  • die SQL-Datei enthält das entsprechende Datenbankschema
  • den Datenbank-Ausführungsplan

Bitte beachten Sie, dass ich das vollständige Datenbankschema oder den Code oder die tatsächlichen Daten, gegen die ich Abfragen durchführen, nicht offenlegen kann. (Die SQL-Tabellen haben weitere Spalten neben den genannten und der C#-Code ist etwas komplexer, weil die Linq-Abfrage bedingt konstruiert ist.)

Update - mehr Einblick zur Laufzeit

Einige Eigenschaften beider DataContext-Instanzen:

Log = null;
Transaction = null;
CommandTimeout = 60;
Connection: System.Data.SqlClient.SqlConnection;

Die SqlConnection wurde aus einer Verbindungszeichenfolge wie dieser erstellt (in beiden Fällen):

"Data Source=server;Initial Catalog=sourceDb;Persist Security Info=True;User ID=user;Password=password"

Es werden keine expliziten SqlCommands ausgeführt, um SET-Optionen an die Datenbanksession zu übergeben. Auch enthalten die inline TVF SET-Optionen nicht.

10voto

Zer0 Punkte 7256

Sie müssen eine Überwachung ausführen auf SQL Server anstatt dies von der C#-Seite aus zu debuggen. Dadurch sehen Sie alles, was sowohl A als auch B auf dem Server ausführen. Der Ausführungsplan bringt Ihnen nichts, weil es genau das ist - nur ein Plan. Sie möchten die genauen Anweisungen und ihre tatsächlichen Leistungsmetriken sehen.

Sollten Sie mir in dem seltenen Fall sagen, dass beide SELECT-Anweisungen genau gleich sind, aber eine stark unterschiedliche Leistung haben, wäre ich praktisch sicher, dass sie unter verschiedenen Transaktionsisolationsstufen ausgeführt werden. Ein einzelner SQL-Befehl ist eine implizite Transaktion, auch wenn Sie keine explizit erstellen.

Sollte aus irgendeinem Grund die Überwachung nicht eindeutig machen, sollten Sie die ausgeführten Befehle zusammen mit ihren Metriken veröffentlichen.

Hinweis: Das Ausführen einer Überwachung verursacht einige Performance-Overheadkosten, daher würde ich versuchen, den Zeitrahmen klein zu halten oder wenn möglich außerhalb der Hauptverkehrszeiten auszuführen.

0voto

Navneet Punkte 439

Ich denke, Sie werden LazyLoadingEnabled="true" in Ihrer "A" Projekt-EDMX-Datei überprüfen.

Wenn LazyLoadingEnabled="true": Im Falle des Lazy Loadings werden verbundene Objekte (Unterobjekte) nicht automatisch mit dem übergeordneten Objekt geladen, bis sie angefordert werden. Standardmäßig unterstützt LINQ das Lazy Loading.

Wenn LazyLoadingEnabled="false": Im Falle des Eager Loadings werden verbundene Objekte (Unterobjekte) automatisch mit dem übergeordneten Objekt geladen. Um Eager Loading zu verwenden, müssen Sie die Include() Methode verwenden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X