Pourquoi une simple boucle entraîne-t-elle des attentes ASYNC_NETWORK_IO?

19

Le T-SQL suivant prend environ 25 secondes sur ma machine avec SSMS v17.9:

DECLARE @outer_loop INT = 0,
@big_string_for_u VARCHAR(8000);

SET NOCOUNT ON;

WHILE @outer_loop < 50000000
BEGIN
    SET @big_string_for_u = 'ZZZZZZZZZZ';
    SET @outer_loop = @outer_loop + 1;
END;

Il cumule 532 ms d' ASYNC_NETWORK_IOattente selon les deux sys.dm_exec_session_wait_statset sys.dm_os_wait_stats. Le temps d'attente total augmente à mesure que le nombre d'itérations de boucle augmente. En utilisant l' wait_completedévénement étendu, je peux voir que l'attente se produit environ toutes les 43 ms, à quelques exceptions près:

table d'attente

De plus, je peux obtenir les piles d'appels qui se produisent juste avant l' ASYNC_NETWORK_IOattente:

sqldk.dll!SOS_DispatcherBase::GetTrack+0x7f6c
sqldk.dll!SOS_Scheduler::PromotePendingTask+0x204
sqldk.dll!SOS_Task::PostWait+0x5f
sqldk.dll!SOS_Scheduler::Suspend+0xb15
sqllang.dll!CSECCNGProvider::GetBCryptHandleFromAlgID+0xf6af
sqllang.dll!CSECCNGProvider::GetBCryptHandleFromAlgID+0xf44c
sqllang.dll!SNIPacketRelease+0xd63
sqllang.dll!SNIPacketRelease+0x2097
sqllang.dll!SNIPacketRelease+0x1f99
sqllang.dll!SNIPacketRelease+0x18fe
sqllang.dll!CAutoExecuteAsContext::Restore+0x52d
sqllang.dll!CSQLSource::Execute+0x151b
sqllang.dll!CSQLSource::Execute+0xe13
sqllang.dll!CSQLSource::Execute+0x474
sqllang.dll!SNIPacketRelease+0x165d
sqllang.dll!CValOdsRow::CValOdsRow+0xa92
sqllang.dll!CValOdsRow::CValOdsRow+0x883
sqldk.dll!ClockHand::Statistic::RecordClockHandStats+0x15d
sqldk.dll!ClockHand::Statistic::RecordClockHandStats+0x638
sqldk.dll!ClockHand::Statistic::RecordClockHandStats+0x2ad
sqldk.dll!SystemThread::MakeMiniSOSThread+0xdf8
sqldk.dll!SystemThread::MakeMiniSOSThread+0xf00
sqldk.dll!SystemThread::MakeMiniSOSThread+0x667
sqldk.dll!SystemThread::MakeMiniSOSThread+0xbb9

Enfin, j'ai remarqué que SSMS utilise une quantité surprenante de CPU pendant la boucle (environ la moitié d'un cœur en moyenne). Je ne peux pas comprendre ce que SSMS fait pendant ce temps.

Pourquoi une simple boucle provoque-t-elle des ASYNC_NETWORK_IOattentes lorsqu'elle est exécutée via SSMS? La seule sortie que je semble obtenir du client à partir de cette exécution de requête est la "Commande terminée avec succès". message.

Joe Obbish
la source

Réponses:

31

La documentation de SET NOCOUNTdit:

SET NOCOUNT ONempêche l'envoi de DONE_IN_PROCmessages au client pour chaque instruction dans une procédure stockée. Pour les procédures stockées qui contiennent plusieurs instructions qui ne renvoient pas beaucoup de données réelles, ou pour les procédures qui contiennent des boucles Transact-SQL, la définition SET NOCOUNTde ONpeut fournir une amélioration significative des performances, car le trafic réseau est considérablement réduit.

Vous n'exécutez pas les instructions dans une procédure stockée, donc SQL Server envoie des DONEjetons (code 0xFD) pour indiquer l'état d'achèvement de chaque instruction SQL. Ces messages sont différés et envoyés de manière asynchrone lorsque le paquet réseau est plein. Lorsque le client ne consomme pas assez rapidement les paquets réseau, les tampons se remplissent finalement et l'opération devient bloquante pour SQL Server, ce qui génère les ASYNC_NETWORK_IOattentes.

Notez que les DONEjetons sont différents de DONEINPROC(code 0xFF) comme le note la documentation:

  • Un DONEjeton est renvoyé pour chaque instruction SQL du lot SQL, à l'exception des déclarations de variables.

  • Pour l' exécution des instructions SQL dans les procédures stockées, DONEPROCet les DONEINPROCjetons sont utilisés à la place de DONEjetons.

Vous verrez une réduction spectaculaire des temps d' ASYNC_NETWORK_IOattente en utilisant:

CREATE PROCEDURE #P AS
SET NOCOUNT ON;

DECLARE
    @outer_loop integer = 0,
    @big_string_for_u varchar(8000);


WHILE @outer_loop < 5000000
BEGIN
    SET @big_string_for_u = 'ZZZZZZZZZZ';
    SET @outer_loop = @outer_loop + 1;
END;
GO
EXECUTE dbo.#P;

Vous pouvez également utiliser sys.sp_executesqlpour obtenir le même résultat.

Exemple de trace de pile capturée au ASYNC_NETWORK_IOdébut d' une attente:

envoi d'un paquet

Un exemple de paquet TDS tel que vu dans la fonction en ligne sqllang!srv_completioncode_ex<1>avait les 13 octets suivants:

fd 01 00 c1 00 01 00 00 00 00 00 00 00          

Qui décode pour:

  • TokenType = 0xfd DONE_TOKEN
  • Statut = 0x0001 DONE_MORE
  • CurCmd = 0x00c1 (193)
  • DoneRowCount = 0x00000001 (1)

En fin de compte, le nombre d' ASYNC_NETWORK_IOattente dépend du client et du pilote, et de ce qu'il fait, le cas échéant, avec tous les DONEmessages. Test avec une boucle 1/10 de la taille indiquée dans la question (5 000 000 itérations de boucle), j'ai trouvé que SSMS fonctionnait pendant environ 4 secondes avec 200 à 300 ms d'attente. sqlcmda fonctionné pendant 2-3 secondes avec des attentes à un chiffre; osqlenviron le même temps d'exécution avec environ 10 ms d'attente.

Le pire client de loin pour ce test était Azure Data Studio. Il a fonctionné pendant près de 6 heures:

LES PUBLICITÉS

Paul White dit GoFundMonica
la source