web-dev-qa-db-ja.com

単純なループによってASYNC_NETWORK_IOが待機するのはなぜですか?

次の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_IOsys.dm_exec_session_wait_statsの両方に従って、532 msのsys.dm_os_wait_stats待機が累積されます。ループの反復回数が増えると、合計待機時間が長くなります。 wait_completed拡張イベントを使用すると、いくつかの例外を除いて、待機が約43 msごとに発生することがわかります。

wait table

さらに、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が何をしているのか理解できません。

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

19
Joe Obbish

documentation for SET NOCOUNTはこう言います:

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

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

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

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

  • ストアドプロシージャ内でSQLステートメントを実行するには、DONEPROCトークンの代わりにDONEINPROCおよび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待機の開始と同時にキャプチャされたスタックトレースの例:

sending a packet

インライン関数sqllang!srv_completioncode_ex<1>にあるTDSパケットの例は、次の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秒間実行されたことがわかりました。 sqlcmdは2〜3秒間実行され、1桁のms待機がありました。 osqlほぼ同じ実行時間で、約10ミリ秒の待機。

このテストで最悪のクライアントはAzure Data Studioでした。それはほぼ6時間走った:

ADS

31
Paul White 9