単純なループの結果、ASYNC_NETWORK_IOが待機するのはなぜですか?


19

次のT-SQLは、SSMS v17.9を搭載したマシンで約25秒かかります。

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;

とのASYNC_NETWORK_IO両方に応じて、532ミリ秒の待機を蓄積sys.dm_exec_session_wait_statssys.dm_os_wait_statsます。ループの反復回数が増えると、合計待機時間が長くなります。wait_completed拡張イベントを使用すると、いくつかの例外を除いて、約43ミリ秒ごとに待機が発生することがわかります。

待機表

さらに、ASYNC_NETWORK_IO待機の直前に発生する呼び出しスタックを取得できます。

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

最後に、SSMSはループ中に驚くほどのCPUを使用していることに気付きました(平均でコアの約半分)。その間、SSMSが何をしているのかわかりません。

単純なループがASYNC_NETWORK_IOSSMSを介して実行されると、なぜ待機が発生するのですか?このクエリ実行からクライアントから取得するように見える唯一の出力は、「コマンドが正常に完了しました」です。メッセージ。

回答:


31

ドキュメントのためには、SET NOCOUNTこう述べています。

SET NOCOUNT ONDONE_IN_PROCストアドプロシージャのステートメントごとにクライアントにメッセージが送信されるのを防ぎます。実際のデータをあまり返さない複数のステートメントを含むストアドプロシージャ、またはTransact-SQLループを含むプロシージャの場合、ネットワークトラフィックが大幅に削減されるため、に設定SET NOCOUNTするとONパフォーマンスが大幅に向上します。

ストアドプロシージャでステートメントを実行していないため、SQL ServerはDONEトークン(コード0xFD)を送信して、各SQLステートメントの完了ステータスを示します。これらのメッセージは延期され、ネットワークパケットがいっぱいになると非同期で送信されます。クライアントがネットワークパケットをすぐに消費しないと、最終的にバッファーがいっぱいになり、操作がSQL Serverのブロックになり、ASYNC_NETWORK_IO待機が発生します。

ドキュメントDONEが注記しているように、トークンはDONEINPROC(code 0xFF)とは異なることに注意してください。

  • DONEトークンは、変数の宣言を除き、SQLバッチ内の各SQL文に対して返されます。

  • ストアドプロシージャ内でSQLステートメントを実行する場合DONEPROCDONEINPROCトークンの代わりにDONEトークンが使用されます。

以下を使用すると、ASYNC_NETWORK_IO待機時間が劇的に短縮されます。

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;

sys.sp_executesql同じ結果を達成するために使用することもできます。

ASYNC_NETWORK_IO待機が始まるとすぐにキャプチャされるスタックトレースの例:

パケットを送信する

インライン関数で見られるTDSパケットの例にsqllang!srv_completioncode_ex<1>は、次の13バイトがありました。

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

デコードするもの:

  • TokenType = 0xfd DONE_TOKEN
  • ステータス= 0x0001 DONE_MORE
  • CurCmd = 0x00c1(193)
  • DoneRowCount = 0x00000001(1)

最終的に、ASYNC_NETWORK_IO待機数はクライアントとドライバー、およびすべてのDONEメッセージに対する処理(ある場合)に依存します。質問で指定されたサイズの1/10のループ(5,000,000ループの繰り返し)でのテストSSMSは、200〜300ミリ秒の待機で約4秒間実行されました。sqlcmd1桁のms待機で2〜3秒実行されました。osql約10ミリ秒の待機で、ほぼ同じ実行時間です。

このテストで圧倒的な最悪のクライアントはAzure Data Studioでした。ほぼ6時間実行されました。

ADS

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.