Ich habe große SQL-Leistungsprobleme bei der Verwendung von asynchronen Aufrufen. Ich habe einen kleinen Fall erstellt, um das Problem zu demonstrieren.
Ich habe eine Datenbank auf einem SQL Server 2016 erstellt, der sich in unserem LAN befindet (also keine lokale Datenbank).
In dieser Datenbank habe ich eine Tabelle WorkingCopy
mit 2 Spalten:
Id (nvarchar(255, PK))
Value (nvarchar(max))
DDL
CREATE TABLE [dbo].[Workingcopy]
(
[Id] [nvarchar](255) NOT NULL,
[Value] [nvarchar](max) NULL,
CONSTRAINT [PK_Workingcopy]
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
In diese Tabelle habe ich einen einzelnen Datensatz eingefügt ( id
= 'PerfUnitTest', Value
ist eine 1,5-MB-Zeichenfolge (eine Zip-Datei eines größeren JSON-Datasets)).
Wenn ich nun die Abfrage in SSMS ausführe:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
Ich erhalte sofort das Ergebnis und sehe im SQL Servre Profiler, dass die Ausführungszeit etwa 20 Millisekunden betrug. Alles normal.
Wenn Sie die Abfrage aus .NET (4.6) -Code mit einem einfachen Code ausführen SqlConnection
:
// at this point, the connection is already open
var command = new SqlCommand($"SELECT Value FROM WorkingCopy WHERE Id = @Id", _connection);
command.Parameters.Add("@Id", SqlDbType.NVarChar, 255).Value = key;
string value = command.ExecuteScalar() as string;
Die Ausführungszeit hierfür beträgt ebenfalls ca. 20-30 Millisekunden.
Aber wenn Sie es in asynchronen Code ändern:
string value = await command.ExecuteScalarAsync() as string;
Die Ausführungszeit beträgt plötzlich 1800 ms ! Auch in SQL Server Profiler sehe ich, dass die Ausführungsdauer der Abfrage mehr als eine Sekunde beträgt. Obwohl die vom Profiler gemeldete ausgeführte Abfrage genau mit der nicht-asynchronen Version identisch ist.
Aber es wird schlimmer. Wenn ich mit der Paketgröße in der Verbindungszeichenfolge herumspiele, erhalte ich die folgenden Ergebnisse:
Paketgröße 32768: [TIMING]: ExecuteScalarAsync im SqlValueStore -> verstrichene Zeit: 450 ms
Paketgröße 4096: [TIMING]: ExecuteScalarAsync im SqlValueStore -> verstrichene Zeit: 3667 ms
Paketgröße 512: [TIMING]: ExecuteScalarAsync im SqlValueStore -> verstrichene Zeit: 30776 ms
30.000 ms !! Das ist über 1000x langsamer als die nicht asynchrone Version. Und SQL Server Profiler meldet, dass die Ausführung der Abfrage mehr als 10 Sekunden gedauert hat. Das erklärt nicht einmal, wohin die anderen 20 Sekunden gehen!
Dann habe ich wieder zur Synchronisierungsversion gewechselt und auch mit der Paketgröße herumgespielt, und obwohl dies die Ausführungszeit ein wenig beeinflusst hat, war es nirgends so dramatisch wie bei der asynchronen Version.
Wenn nur eine kleine Zeichenfolge (<100 Byte) in den Wert eingefügt wird, ist die Ausführung der asynchronen Abfrage genauso schnell wie die Synchronisierungsversion (Ergebnis 1 oder 2 ms).
Das verwirrt mich wirklich, zumal ich das eingebaute SqlConnection
ORM verwende, nicht einmal ein ORM. Auch beim Durchsuchen habe ich nichts gefunden, was dieses Verhalten erklären könnte. Irgendwelche Ideen?
GetSqlChars
oder GetSqlBinary
rufen Sie sie auf Streaming-Weise ab. Erwägen Sie auch, sie als FILESTREAM-Daten zu speichern - es gibt keinen Grund, 1,5 MB Daten auf der Datenseite einer Tabelle zu speichern