Je vais avoir des grands SQL problèmes de performances lors de l'utilisation d'appels asynchrones. J'ai créé un petit cas pour illustrer le problème.
J'ai créer une base de données sur un Serveur SQL server 2016 qui réside dans notre réseau local (donc pas d'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 de 1,5 mo chaîne de caractères (un zip d'un plus grand JSON dataset)).
Maintenant, si j'exécute la requête dans SSMS :
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
J'ai immédiatement obtenir le résultat, et je vois dans SQL Servre Profiler que le temps d'exécution était de près de 20 millisecondes. Tout à fait normal.
Lors de l'exécution de la requête .NET (4.6) code à l'aide d'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 de cela, c'est aussi autour de 20 à 30 millisecondes.
Mais quand le changement de async code :
string value = await command.ExecuteScalarAsync() as string;
Le temps d'exécution est soudainement 1800 ms ! Également dans SQL Server Profiler, je vois que l'exécution de la requête durée est de plus d'une seconde. Bien que la requête exécutée rapporté par le profileur est exactement le même que le non-Async version.
Mais il y a pire. Si je jouer avec la Taille des Paquets dans la chaîne de connexion, j'obtiens les résultats suivants :
Taille de paquet 32768 : [CALENDRIER]: ExecuteScalarAsync dans SqlValueStore -> temps écoulé : 450 ms
Taille de paquet 4096 : [CALENDRIER]: ExecuteScalarAsync dans SqlValueStore -> temps écoulé : 3667 ms
La taille des paquets de 512 : [CALENDRIER]: ExecuteScalarAsync dans SqlValueStore -> temps écoulé : 30776 ms
De 30 000 ms!! C'est au cours d'une 1000x plus lent que le non-async version. Et SQL Server Profiler les rapports que l'exécution de la requête a pris plus de 10 secondes. Qui n'a même pas expliquer d'où les 20 secondes sont partis!
Puis j'ai changé de revenir à la version synchronisée et aussi joué un peu avec la Taille du Paquet, et bien qu'il n'ait d'impact un peu le temps d'exécution, il n'était pas aussi dramatique qu'avec la version asynchrone.
Au passage, si il mets juste un petit string (< 100bytes) dans la valeur, la async l'exécution de la requête est tout aussi rapide que la version synchronisée (résultat dans 1 ou 2 ms).
Je suis vraiment confus par cela, surtout depuis que je suis en utilisant le haut- SqlConnection
, même pas un ORM. Aussi lors de la recherche autour, je n'ai rien trouvé qui pourrait expliquer ce comportement. Des idées?