J'ai des problèmes de performances SQL majeurs lors de l'utilisation d'appels asynchrones. J'ai créé un petit cas pour démontrer le problème.
J'ai créé une base de données sur un SQL Server 2016 qui réside dans notre LAN (donc pas un localDB).
Dans cette base de données, j'ai une table WorkingCopy
avec 2 colonnes:
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]
Dans ce tableau, j'ai inséré un seul enregistrement ( id
= 'PerfUnitTest', Value
est une chaîne de 1,5 Mo (un zip d'un ensemble de données JSON plus grand)).
Maintenant, si j'exécute la requête dans SSMS:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
J'obtiens immédiatement le résultat et je vois dans SQL Servre Profiler que le temps d'exécution était d'environ 20 millisecondes. Tout est normal.
Lors de l'exécution de la requête à partir du code .NET (4.6) en utilisant 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;
Le temps d'exécution pour cela est également d'environ 20-30 millisecondes.
Mais lors du changement en code asynchrone:
string value = await command.ExecuteScalarAsync() as string;
Le temps d'exécution est soudainement de 1800 ms ! Également dans SQL Server Profiler, je vois que la durée d'exécution de la requête est supérieure à une seconde. Bien que la requête exécutée signalée par le profileur soit exactement la même que la version non asynchrone.
Mais ça empire. Si je joue avec la taille du paquet dans la chaîne de connexion, j'obtiens les résultats suivants:
Taille de paquet 32768: [TIMING]: ExecuteScalarAsync in SqlValueStore -> temps écoulé: 450 ms
Packet Size 4096: [TIMING]: ExecuteScalarAsync in SqlValueStore -> temps écoulé: 3667 ms
Taille de paquet 512: [TIMING]: ExecuteScalarAsync in SqlValueStore -> temps écoulé: 30776 ms
30 000 ms !! C'est plus de 1000 fois plus lent que la version non asynchrone. Et SQL Server Profiler signale que l'exécution de la requête a pris plus de 10 secondes. Cela n'explique même pas où sont passées les 20 autres secondes!
Ensuite, je suis revenu à la version de synchronisation et j'ai également joué avec la taille de paquet, et bien que cela ait eu un peu d'impact sur le temps d'exécution, ce n'était nulle part aussi dramatique qu'avec la version asynchrone.
En passant, s'il ne met qu'une petite chaîne (<100 octets) dans la valeur, l'exécution de la requête asynchrone est tout aussi rapide que la version de synchronisation (résultat en 1 ou 2 ms).
Je suis vraiment déconcerté par cela, d'autant plus que j'utilise le intégré SqlConnection
, pas même un ORM. Aussi lors de mes recherches, je n'ai rien trouvé qui puisse expliquer ce comportement. Des idées?