Mam poważne problemy z wydajnością SQL podczas korzystania z wywołań asynchronicznych. Stworzyłem mały przypadek, aby zademonstrować problem.
Utworzyłem bazę danych na serwerze SQL Server 2016, który znajduje się w naszej sieci LAN (a więc nie w lokalnej bazie danych).
W tej bazie danych mam tabelę WorkingCopy
z 2 kolumnami:
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]
W tej tabeli wstawiłem pojedynczy rekord ( id
= 'PerfUnitTest', Value
to ciąg o rozmiarze 1,5 MB (plik ZIP większego zestawu danych JSON)).
Teraz, jeśli wykonam zapytanie w SSMS:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
Natychmiast otrzymuję wynik i widzę w SQL Servre Profiler, że czas wykonania wyniósł około 20 milisekund. Wszystko normalne.
Podczas wykonywania zapytania z kodu .NET (4.6) przy użyciu zwykłego 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;
Czas wykonania tego również wynosi około 20-30 milisekund.
Ale zmieniając go na kod asynchroniczny:
string value = await command.ExecuteScalarAsync() as string;
Czas wykonania jest nagle 1800 ms ! Również w SQL Server Profiler widzę, że czas wykonania zapytania jest dłuższy niż sekunda. Chociaż wykonane zapytanie zgłoszone przez profilera jest dokładnie takie samo, jak wersja inna niż Async.
Ale jest gorzej. Jeśli bawię się rozmiarem pakietu w ciągu połączenia, otrzymuję następujące wyniki:
Rozmiar pakietu 32768: [TIMING]: ExecuteScalarAsync w SqlValueStore -> czas, który upłynął: 450 ms
Rozmiar pakietu 4096: [TIMING]: ExecuteScalarAsync w SqlValueStore -> czas, który upłynął: 3667 ms
Rozmiar pakietu 512: [TIMING]: ExecuteScalarAsync w SqlValueStore -> czas, który upłynął: 30776 ms
30 000 ms !! To ponad 1000 razy wolniej niż wersja nie-asynchroniczna. SQL Server Profiler zgłasza, że wykonanie zapytania zajęło ponad 10 sekund. To nawet nie wyjaśnia, dokąd upłynęło pozostałe 20 sekund!
Potem wróciłem do wersji synchronizowanej i bawiłem się rozmiarem pakietu i chociaż wpłynęło to trochę na czas wykonywania, nigdzie nie było tak dramatycznego, jak w przypadku wersji asynchronicznej.
Na marginesie, jeśli wstawi tylko mały ciąg (<100 bajtów) do wartości, wykonanie zapytania asynchronicznego będzie tak samo szybkie, jak wersja synchronizacji (wynik w 1 lub 2 ms).
Jestem tym naprawdę zdumiony, zwłaszcza że używam wbudowanego SqlConnection
, a nawet ORM. Również podczas poszukiwań nie znalazłem nic, co mogłoby wyjaśnić to zachowanie. Jakieś pomysły?