При использовании асинхронных вызовов у меня возникают серьезные проблемы с производительностью 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,5 МБ (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 я вижу, что продолжительность выполнения запроса больше секунды. Хотя выполненный запрос, сообщаемый профилировщиком, точно такой же, как и неасинхронная версия.
Но становится еще хуже. Если я поиграю с размером пакета в строке подключения, я получу следующие результаты:
Размер пакета 32768: [ВРЕМЯ]: ExecuteScalarAsync в SqlValueStore -> прошедшее время: 450 мс
Размер пакета 4096: [ВРЕМЯ]: ExecuteScalarAsync в SqlValueStore -> прошедшее время: 3667 мс
Размер пакета 512: [ВРЕМЯ]: ExecuteScalarAsync в SqlValueStore -> прошедшее время: 30776 мс
30 000 мс !! Это более чем в 1000 раз медленнее, чем неасинхронная версия. И SQL Server Profiler сообщает, что выполнение запроса заняло более 10 секунд. Это даже не объясняет, куда ушли остальные 20 секунд!
Затем я вернулся к версии с синхронизацией и также поигрался с размером пакета, и хотя это немного повлияло на время выполнения, оно не было таким драматичным, как с асинхронной версией.
В качестве примечания, если в значение помещается только небольшая строка (<100 байт), выполнение асинхронного запроса выполняется так же быстро, как и версия синхронизации (результат составляет 1 или 2 мс).
Меня это действительно сбивает с толку, тем более что я использую встроенный SqlConnection, даже не ORM. Также при поиске я не нашел ничего, что могло бы объяснить такое поведение. Любые идеи?