it-swarm.com.de

SQL-Abfrage in .NET-Anwendung langsam, in SQL Server Management Studio jedoch sofort

Hier ist die SQL

SELECT tal.TrustAccountValue
FROM TrustAccountLog AS tal
INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID
INNER JOIN Users usr ON usr.UserID = ta.UserID
WHERE usr.UserID = 70402 AND
ta.TrustAccountID = 117249 AND
tal.trustaccountlogid =  
(
 SELECT MAX (tal.trustaccountlogid)
 FROM  TrustAccountLog AS tal
 INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID
 INNER JOIN Users usr ON usr.UserID = ta.UserID
 WHERE usr.UserID = 70402 AND
 ta.TrustAccountID = 117249 AND
 tal.TrustAccountLogDate < '3/1/2010 12:00:00 AM'
)

Grundsätzlich gibt es eine Users-Tabelle, eine TrustAccount-Tabelle und eine TrustAccountLog-Tabelle.
Benutzer: Enthält Benutzer und deren Details
TrustAccount: Ein Benutzer kann mehrere TrustAccounts haben.
TrustAccountLog: Enthält ein Audit aller TrustAccount "Bewegungen". EIN
TrustAccount ist mehreren TrustAccountLog-Einträgen zugeordnet. Jetzt wird diese Abfrage in SQL Server Management Studio in Millisekunden ausgeführt, aber aus irgendeinem seltsamen Grund dauert es in meiner C # -Anwendung ewig und manchmal sogar eine Zeitüberschreitung (120 Sekunden).

Hier ist der Code auf den Punkt gebracht. Es wird mehrmals in einer Schleife aufgerufen und die Anweisung wird vorbereitet.

cmd.CommandTimeout = Configuration.DBTimeout;
cmd.CommandText = "SELECT tal.TrustAccountValue FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = @UserID1 AND ta.TrustAccountID = @TrustAccountID1 AND tal.trustaccountlogid =  (SELECT MAX (tal.trustaccountlogid) FROM  TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = @UserID2 AND ta.TrustAccountID = @TrustAccountID2 AND tal.TrustAccountLogDate < @TrustAccountLogDate2 ))";
cmd.Parameters.Add("@TrustAccountID1", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID1", SqlDbType.Int).Value = userId;
cmd.Parameters.Add("@TrustAccountID2", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID2", SqlDbType.Int).Value = userId;
cmd.Parameters.Add("@TrustAccountLogDate2", SqlDbType.DateTime).Value =TrustAccountLogDate;

// And then...

reader = cmd.ExecuteReader();
if (reader.Read())
{
   double value = (double)reader.GetValue(0);
   if (System.Double.IsNaN(value))
      return 0;
   else
      return value;
}
else
   return 0;
56
n4rzul

Wenn dies Parameter-Sniffing ist, versuchen Sie, option(recompile) am Ende Ihrer Abfrage einzufügen. Ich würde empfehlen, eine gespeicherte Prozedur zu erstellen, um die Logik besser verwalten zu können. Auch einverstanden - warum übergeben Sie 5 Parameter, wenn Sie nur drei benötigen, gemessen am Beispiel? Können Sie stattdessen diese Abfrage verwenden?

select TrustAccountValue from
(
 SELECT MAX (tal.trustaccountlogid), tal.TrustAccountValue
 FROM  TrustAccountLog AS tal
 INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID
 INNER JOIN Users usr ON usr.UserID = ta.UserID
 WHERE usr.UserID = 70402 AND
 ta.TrustAccountID = 117249 AND
 tal.TrustAccountLogDate < '3/1/2010 12:00:00 AM'
 group by tal.TrustAccountValue
) q

Und je nach den Spracheinstellungen des Benutzers, der die Abfrage ausführt, verwenden Sie ein mehrdeutiges Datumsformat. Für mich ist dies zum Beispiel der 3. Januar, nicht der 1. März. Überprüfen Sie dies heraus:

set language us_english
go
select @@language --us_english
select convert(datetime, '3/1/2010 12:00:00 AM')
go
set language british
go
select @@language --british
select convert(datetime, '3/1/2010 12:00:00 AM')

Der empfohlene Ansatz ist die Verwendung des ISO-Formats yyyymmdd hh: mm: ss

select convert(datetime, '20100301 00:00:00') --midnight 00, noon 12
28
Piotr Rodak

Nach meiner Erfahrung ist der übliche Grund, warum eine Abfrage in SSMS schnell und in .NET langsam ausgeführt wird, auf Unterschiede in den SET -Einstellungen der Verbindung zurückzuführen. Wenn eine Verbindung von SSMS oder SqlConnection geöffnet wird, werden automatisch eine Reihe von SET Befehlen ausgegeben, um die Ausführungsumgebung einzurichten. Leider haben SSMS und SqlConnection unterschiedliche SET Standardeinstellungen.

Ein häufiger Unterschied ist SET ARITHABORT. Versuchen Sie es mit SET ARITHABORT ON als erster Befehl aus Ihrem .NET-Code.

Mit SQL Profiler können Sie überwachen, welche SET -Befehle sowohl von SSMS als auch von .NET ausgegeben werden, damit Sie andere Unterschiede feststellen können.

Der folgende Code zeigt, wie ein Befehl SET ausgegeben wird. Beachten Sie jedoch, dass dieser Code nicht getestet wurde.

using (SqlConnection conn = new SqlConnection("<CONNECTION_STRING>")) {
    conn.Open();

    using (SqlCommand comm = new SqlCommand("SET ARITHABORT ON", conn)) {
        comm.ExecuteNonQuery();
    }

    // Do your own stuff here but you must use the same connection object
    // The SET command applies to the connection. Any other connections will not
    // be affected, nor will any new connections opened. If you want this applied
    // to every connection, you must do it every time one is opened.
}
64
Daniel Renshaw

Hatte das gleiche Problem in einer Testumgebung, obwohl das Live-System (auf demselben SQL Server) einwandfrei lief. Das Hinzufügen von OPTION (RECOMPILE) und OPTION (OPTIMIZE FOR (@ p1 UNKNOWN)) hat nicht geholfen.

Ich habe SQL Profiler verwendet, um die genaue Abfrage abzufangen, die der .NET-Client gesendet hat, und festgestellt, dass diese Abfrage mit exec sp_executesql N'select ... Umbrochen wurde und dass die Parameter als nvarchars deklariert wurden - die verglichenen Spalten sind einfache varchars.

Durch das Einfügen des erfassten Abfragetexts in SSMS wurde bestätigt, dass er genauso langsam ausgeführt wird wie auf dem .NET-Client.

Ich stellte fest, dass das Ändern des Parametertyps in AnsiText das Problem behebt:

p = cm.CreateParameter() p.ParameterName = "@company" p.Value = company p.DbType = DbType.AnsiString cm.Parameters.Add(p)

Ich konnte nie erklären, warum die Test- und Live-Umgebung so deutliche Leistungsunterschiede aufwiesen.

11
Daz

Ich hoffe, Ihr spezielles Problem ist inzwischen behoben, da es sich um einen alten Beitrag handelt.

Das Befolgen von SET -Optionen kann sich auf die Wiederverwendung des Plans auswirken (vollständige Liste am Ende).

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
SET ARITHABORT ON
GO

Die folgenden zwei Anweisungen stammen von msdn - SET ARITHABORT

Das Setzen von ARITHABORT auf OFF kann sich negativ auf die Abfrageoptimierung auswirken und zu Leistungsproblemen führen.

Die Standardeinstellung von ARITHABORT für SQL Server Management Studio ist ON. Client-Anwendungen, bei denen ARITHABORT auf OFF gesetzt ist, können unterschiedliche Abfragepläne empfangen, wodurch die Behandlung von Abfragen mit schlechter Leistung erschwert wird. Das heißt, die gleiche Abfrage kann in Management Studio schnell ausgeführt werden, in der Anwendung jedoch langsam.

Ein weiteres interessantes Thema ist Parameter Sniffing wie in Langsam in der Anwendung, Schnell in SSMS? Performance Mysteries verstehen - von Erland Sommarskog

Eine weitere Möglichkeit ist die (interne) Konvertierung von VARCHAR-Spalten in NVARCHAR unter Verwendung des Unicode-Eingabeparameters (siehe Fehlerbehebung bei der SQL-Indexleistung für varchar-Spalten - von Jimmy Bogard

FÜR UNBEKANNTES OPTIMIEREN

Berücksichtigen Sie in SQL Server 2008 und höher OPTIMIZE FOR UNKNOWN. UNBEKANNT: Gibt an, dass das Abfrageoptimierungsprogramm statistische Daten anstelle des Anfangswerts verwendet, um den Wert für eine lokale Variable während der Abfrageoptimierung zu ermitteln.

OPTION (RECOMPILE)

Verwenden Sie "OPTION (RECOMPILE)" anstelle von "WITH RECOMPILE", wenn nur eine Neukompilierung möglich ist. Es hilft bei der Optimierung der Parameter-Einbettung. Lesen Sie Parameter Sniffing, Embedding und die RECOMPILE-Optionen - von Paul White

SET-Optionen

Das Befolgen der SET -Optionen kann sich auf die Wiederverwendung von Plänen auswirken, basierend auf msdn - Plan Caching in SQL Server 2008

  1. ANSI_NULL_DFLT_OFF 2. ANSI_NULL_DFLT_ON 3. ANSI_NULLS 4. ANSI_PADDING 5. ANSI_WARNINGS 6. ARITHABORT 7. CONCAT_NULL_YIELDS_NUL 8. DATEFIRST 9. DATEFORMAT 10. FORCEPLAN 11. SPRACHE 12. NO_BROENTIERBETABLE 11. LANGUET
7
Lijo

Höchstwahrscheinlich liegt das Problem im Kriterium

tal.TrustAccountLogDate < @TrustAccountLogDate2

Der optimale Ausführungsplan hängt stark vom Wert des Parameters ab. Wenn Sie 1910-01-01 (das keine Zeilen zurückgibt) übergeben, wird mit Sicherheit ein anderer Plan als 2100-12-31 (das alle Zeilen zurückgibt) erstellt.

Wenn der Wert in der Abfrage als Literal angegeben wird, weiß SQL Server, welcher Wert während der Planerstellung verwendet werden soll. Wenn ein Parameter verwendet wird, generiert der SQL Server den Plan nur einmal und verwendet ihn dann erneut. Wenn der Wert in einer nachfolgenden Ausführung zu stark vom ursprünglichen Wert abweicht, ist der Plan nicht optimal.

Um Abhilfe zu schaffen, können Sie in der Abfrage OPTION(RECOMPILE) angeben. Das Hinzufügen der Abfrage zu einer gespeicherten Prozedur wird Ihnen bei diesem speziellen Problem nicht helfen, es sei denn, Sie erstellen die Prozedur WITH RECOMPILE.

Andere haben dies bereits erwähnt ("Parameter Sniffing"), aber ich dachte, eine einfache Erklärung des Konzepts schadet nicht.

6
erikkallen

Es könnte sich um Typkonvertierungsprobleme handeln. Sind alle IDs wirklich SqlDbType.Int auf der Datenebene?

Warum gibt es 4 Parameter, von denen 2 ausreichen?

cmd.Parameters.Add("@TrustAccountID1", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID1", SqlDbType.Int).Value = userId;
cmd.Parameters.Add("@TrustAccountID2", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID2", SqlDbType.Int).Value = userId;

Könnte sein

cmd.Parameters.Add("@TrustAccountID", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID", SqlDbType.Int).Value = userId;

Da ihnen beide die gleiche Variable zugewiesen sind.

(Dies kann dazu führen, dass der Server einen anderen Plan erstellt, da er vier verschiedene Variablen als op. Bis. 4-Konstanten erwartet. Die Angabe von 2 Variablen kann einen Unterschied für die Serveroptimierung bewirken.)

3
Hogan

Da Sie anscheinend immer nur den Wert einer Zeile aus einer Spalte zurückgeben, können Sie stattdessen ExecuteScalar () für das Befehlsobjekt verwenden, was effizienter sein sollte:

    object value = cmd.ExecuteScalar();

    if (value == null)
        return 0;
    else
        return (double)value;
2
Dan Diplo

In meinem Fall bestand das Problem darin, dass mein Entity Framework Abfragen generiert hat, die exec sp_executesql Verwenden.

Wenn die Parameter im Typ nicht genau übereinstimmen, verwendet der Ausführungsplan keine Indizes, da er entscheidet, die Konvertierung in die Abfrage selbst zu übernehmen. Wie Sie sich vorstellen können, führt dies zu einer viel langsameren Leistung.

in meinem Fall wurde die Spalte als CHR (3) definiert und das Entity Framework hat N'str 'in der Abfrage übergeben, was eine Konvertierung von nchar nach char verursacht. Also für eine Abfrage, die so aussieht:

ctx.Events.Where(e => e.Status == "Snt")

Es wurde eine SQL-Abfrage generiert, die ungefähr so ​​aussieht:

FROM [ExtEvents] AS [Extent1] ... WHERE (N''Snt'' = [Extent1].[Status]) ...

In meinem Fall war es die einfachste Lösung, den Spaltentyp zu ändern. Alternativ können Sie mit Ihrem Code ringen, damit er überhaupt den richtigen Typ enthält.

2
Eyal

Klingt möglicherweise nach Parameter-Sniffing? Haben Sie versucht, genau zu erfassen, was der Client-Code an SQL Server sendet (verwenden Sie den Profiler, um die genaue Anweisung abzufangen), und führen Sie diese dann in Management Studio aus?

Parameter-Sniffing: Leistung des Ausführungsplans für SQL-arme gespeicherte Prozeduren - Parameter-Sniffing

Ich habe das noch nie in Code gesehen, nur in Prozeduren, aber es ist einen Blick wert.

1
Meff

Ich hatte dieses Problem heute und dies löste mein Problem: https: //www.mssqltips.com/sqlservertip/4318/sql-server-stored-procedure-runs-fast-in-ssms-and-slow -in-application/

Ich habe den Anfang von SP this: Set ARITHABORT ON

Holp dies dir helfen!

1
Italo Reis

Sie scheinen Ihren Datenleser nicht zu schließen - dies könnte sich über eine Reihe von Iterationen summieren ...

0
Paddy

Mir ist klar, dass das OP die Verwendung gespeicherter Prozeduren nicht erwähnt, aber es gibt eine alternative Lösung für Parameter-Sniffing-Probleme bei der Verwendung gespeicherter Prozeduren, die weniger elegant ist, aber bei mir funktioniert hat, wenn OPTION(RECOMPILE) nicht zu funktionieren scheint etwas.

Kopieren Sie einfach Ihre Parameter in Variablen, die in der Prozedur deklariert wurden, und verwenden Sie diese stattdessen.

Beispiel:

ALTER PROCEDURE [ExampleProcedure]
@StartDate DATETIME,
@EndDate DATETIME
AS
BEGIN

--reassign to local variables to avoid parameter sniffing issues
DECLARE @MyStartDate datetime,
        @MyEndDate datetime

SELECT 
    @MyStartDate = @StartDate,
    @MyEndDate = @EndDate

--Rest of procedure goes here but refer to @MyStartDate and @MyEndDate
END
0
GlacialSpoon

Ich hatte ein Problem mit einer anderen Ursache, die genau dem Titel der Symptome dieser Frage entsprach.

In meinem Fall bestand das Problem darin, dass die Ergebnismenge vom .NET-Code der Anwendung offengehalten wurde, während durch jeden zurückgegebenen Datensatz geschleift und weitere drei Abfragen für die Datenbank ausgeführt wurden! In mehreren tausend Zeilen sah die ursprüngliche Abfrage aufgrund der Zeitinformationen von SQL Server irreführend so aus, als wäre sie nur langsam ausgeführt worden.

Das Update bestand daher darin, den .NET-Code, der die Aufrufe ausführt, so umzugestalten, dass die Ergebnismenge während der Verarbeitung jeder Zeile nicht geöffnet bleibt.

0
Tim Abell