为什么简单循环会导致ASYNC_NETWORK_IO等待?


19

在使用SSMS v17.9的计算机上,以下T-SQL大约需要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根据sys.dm_exec_session_wait_stats和,它累计532毫秒的等待时间sys.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_IO通过SSMS执行简单循环会导致等待?我似乎从此查询执行中从客户端获得的唯一输出是“命令已成功完成”。信息。

Answers:


31

文档SET NOCOUNT说:

SET NOCOUNT ON防止DONE_IN_PROC在存储过程中为每个语句向客户端发送消息。对于包含多个不会返回大量实际数据的语句的存储过程,或者对于包含Transact-SQL循环的过程,将设置SET NOCOUNTON可以显着提高性能,因为大大减少了网络流量。

您没有在存储过程中运行语句,因此SQL Server发送DONE令牌(代码0xFD)以指示每个SQL语句的完成状态。这些消息将被延迟,并在网络数据包已满时异步发送。当客户端没有足够快地消耗网络数据包时,最终缓冲区将填满,并且该操作对SQL Server变得阻塞,从而生成ASYNC_NETWORK_IO等待。

请注意,DONE令牌与文档中的DONEINPROC(code 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等待等待开始时捕获的示例堆栈跟踪:

发送数据包

内联函数中看到的示例TDS数据包sqllang!srv_completioncode_ex<1>具有以下13个字节:

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

解码为:

  • 令牌类型= 0xfd DONE_TOKEN
  • 状态= 0x0001 DONE_MORE
  • CurCmd = 0x00c1(193)
  • DoneRowCount = 0x00000001(1)

最终,ASYNC_NETWORK_IO等待的次数取决于客户端和驱动程序,以及对所有DONE消息的处理方式(如果有的话)。使用问题给出的大小的1/10(5,000,000次循环迭代)进行测试,我发现SSMS在200-300 ms的等待时间内运行了大约4秒钟。sqlcmd用一位数的ms等待运行2-3秒;osql在大约10毫秒的等待时间内达到相同的运行时间。

到目前为止,此测试中最差的客户端是Azure Data Studio。它运行了将近6个小时:

美国存托凭证

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.