Sto riscontrando gravi problemi di prestazioni SQL durante l'utilizzo di chiamate asincrone. Ho creato un piccolo case per dimostrare il problema.
Ho creato un database su un SQL Server 2016 che risiede nella nostra LAN (quindi non un database locale).
In quel database, ho una tabella WorkingCopy
con 2 colonne:
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]
In quella tabella ho inserito un singolo record (id
= 'PerfUnitTest', Value
è una stringa da 1,5 MB (uno zip di un set di dati JSON più grande)).
Ora, se eseguo la query in SSMS:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
Ottengo immediatamente il risultato e vedo in SQL Servre Profiler che il tempo di esecuzione è stato di circa 20 millisecondi. Tutto normale.
Quando si esegue la query dal codice .NET (4.6) utilizzando un semplice 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;
Anche il tempo di esecuzione per questo è di circa 20-30 millisecondi.
Ma quando lo si modifica in codice asincrono:
string value = await command.ExecuteScalarAsync() as string;
Il tempo di esecuzione è improvvisamente 1800 ms ! Anche in SQL Server Profiler, vedo che la durata dell'esecuzione della query è superiore a un secondo. Sebbene la query eseguita riportata dal profiler sia esattamente la stessa della versione non Async.
Ma c'è di peggio. Se gioco con la dimensione del pacchetto nella stringa di connessione, ottengo i seguenti risultati:
Dimensione pacchetto 32768: [TIMING]: ExecuteScalarAsync in SqlValueStore -> tempo trascorso: 450 ms
Packet Size 4096: [TIMING]: ExecuteScalarAsync in SqlValueStore -> tempo trascorso: 3667 ms
Dimensione pacchetto 512: [TIMING]: ExecuteScalarAsync in SqlValueStore -> tempo trascorso: 30776 ms
30.000 ms !! È oltre 1000 volte più lento rispetto alla versione non asincrona. Inoltre, SQL Server Profiler segnala che l'esecuzione della query ha richiesto più di 10 secondi. Questo non spiega nemmeno dove sono finiti gli altri 20 secondi!
Poi sono tornato alla versione di sincronizzazione e ho anche giocato con la dimensione del pacchetto, e sebbene abbia influito un po 'sul tempo di esecuzione, non è stato così drammatico come con la versione asincrona.
Come nota a margine, se inserisce solo una piccola stringa (<100 byte) nel valore, l'esecuzione della query asincrona è altrettanto veloce della versione di sincronizzazione (risultato in 1 o 2 ms).
Sono davvero sconcertato da questo, soprattutto perché sto usando il built-in SqlConnection
, nemmeno un ORM. Inoltre, durante la ricerca in giro, non ho trovato nulla che potesse spiegare questo comportamento. Qualche idea?