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

Joe*_*ish 22 sql-server ssms

以下 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;
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

最后,我注意到 SSMS 在循环期间使用了惊人数量的 CPU(平均大约一半内核)。我无法弄清楚 SSMS 在那段时间在做什么。

为什么一个简单的循环ASYNC_NETWORK_IO在通过 SSMS 执行时会导致等待?我似乎从这个查询执行中从客户端获得的唯一输出是“命令成功完成”。信息。

Pau*_*ite 34

文档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语句,执行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;
Run Code Online (Sandbox Code Playgroud)

您也可以使用sys.sp_executesql来实现相同的结果。

ASYNC_NETWORK_IO等待开始时捕获的示例堆栈跟踪:

发送数据包

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

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;
Run Code Online (Sandbox Code Playgroud)

解码为:

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

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

迄今为止,此测试中最糟糕的客户端是 Azure Data Studio。它运行了将近 6 个小时:

广告系统