У меня возникают серьезные проблемы с производительностью SQL при использовании асинхронных вызовов. Я создал небольшой случай, чтобы продемонстрировать проблему.
Я создал базу данных на SQL Server 2016, которая находится в нашей локальной сети (а не в localDB).
В этой базе данных у меня есть таблица WorkingCopy
с двумя столбцами:
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]
В этой таблице я вставил одну запись (id
= 'PerfUnitTest', Value
- это строка 1.5mb (zip большего набора данных JSON)).
Теперь, если я выполняю запрос в SSMS:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
Я сразу получаю результат, и я вижу в SQL Servre Profiler, что время выполнения составляет около 20 миллисекунд. Все нормально.
При выполнении запроса из кода .NET(4.6) с помощью простого 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;
Время выполнения для этого также составляет около 20-30 миллисекунд.
Но при смене его на асинхронный код:
string value = await command.ExecuteScalarAsync() as string;
Время выполнения внезапно 1800 мс! Также в SQL Server Profiler я вижу, что продолжительность выполнения запроса больше секунды. Хотя выполненный запрос, сообщенный профилировщиком, точно такой же, как версия, отличная от Async.
Но все ухудшается. Если я играю с размером пакета в строке подключения, я получаю следующие результаты:
Размер пакета 32768: [ВРЕМЯ]: ExecuteScalarAsync в SqlValueStore → прошедшее время: 450 мс
Размер пакета 4096: [ВРЕМЯ]: ExecuteScalarAsync в SqlValueStore → прошедшее время: 3667 мс
Размер пакета 512: [TIMING]: ExecuteScalarAsync в SqlValueStore → прошедшее время: 30776 мс
30 000 мс! Это на 1000 раз медленнее, чем не-асинхронная версия. И SQL Server Profiler сообщает, что выполнение запроса заняло более 10 секунд. Это даже не объясняет, куда уходят другие 20 секунд!
Затем я переключился на версию синхронизации, а также играл с размером пакета, и хотя это немного повлияло на время выполнения, это было нигде не столь драматично, как с асинхронной версией.
В качестве побочного элемента, если он помещает только маленькую строку (< 100 байт) в значение, выполнение асинхронного запроса выполняется так же быстро, как версия синхронизации (результат составляет 1 или 2 мс).
Я действительно смущен этим, тем более, что я использую встроенный SqlConnection
, даже не ORM. Кроме того, при поиске вокруг я не нашел ничего, что могло бы объяснить это поведение. Любые идеи?