Tengo problemas importantes de rendimiento de SQL cuando uso llamadas asíncronas. He creado un pequeño caso para demostrar el problema.
He creado una base de datos en un SQL Server 2016 que reside en nuestra LAN (por lo que no es un localDB).
En esa base de datos, tengo una tabla WorkingCopy
con 2 columnas:
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]
En esa tabla, he insertado un solo registro ( id
= 'PerfUnitTest', Value
es una cadena de 1.5mb (un archivo zip de un conjunto de datos JSON más grande)).
Ahora, si ejecuto la consulta en SSMS:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
Inmediatamente obtengo el resultado, y veo en SQL Servre Profiler que el tiempo de ejecución fue de alrededor de 20 milisegundos. Todo normal.
Al ejecutar la consulta desde el código .NET (4.6) usando un simple 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;
El tiempo de ejecución para esto también es de alrededor de 20-30 milisegundos.
Pero al cambiarlo a código asincrónico:
string value = await command.ExecuteScalarAsync() as string;
¡El tiempo de ejecución es de repente 1800 ms ! También en SQL Server Profiler, veo que la duración de la ejecución de la consulta es más de un segundo. Aunque la consulta ejecutada informada por el generador de perfiles es exactamente la misma que la versión no Async.
Pero empeora. Si juego con el tamaño del paquete en la cadena de conexión, obtengo los siguientes resultados:
Tamaño de paquete 32768: [TIEMPO]: ExecuteScalarAsync en SqlValueStore -> tiempo transcurrido: 450 ms
Tamaño de paquete 4096: [TIEMPO]: ExecuteScalarAsync en SqlValueStore -> tiempo transcurrido: 3667 ms
Tamaño de paquete 512: [TIEMPO]: ExecuteScalarAsync en SqlValueStore -> tiempo transcurrido: 30776 ms
30.000 ms !! Eso es 1000 veces más lento que la versión no asíncrona. Y SQL Server Profiler informa que la ejecución de la consulta tomó más de 10 segundos. ¡Eso ni siquiera explica a dónde se han ido los otros 20 segundos!
Luego volví a la versión sincronizada y también jugué con el tamaño del paquete, y aunque afectó un poco el tiempo de ejecución, no fue tan dramático como con la versión asincrónica.
Como nota al margen, si coloca solo una pequeña cadena (<100bytes) en el valor, la ejecución de la consulta asíncrona es tan rápida como la versión de sincronización (el resultado es 1 o 2 ms).
Estoy realmente desconcertado por esto, especialmente porque estoy usando el integrado SqlConnection
, ni siquiera un ORM. Además, al buscar, no encontré nada que pudiera explicar este comportamiento. ¿Algunas ideas?