Почему простой цикл приводит к ожиданию ASYNC_NETWORK_IO?

19

Следующий T-SQL занимает около 25 секунд на моей машине с 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;

Он накапливает 532 мс ASYNC_NETWORK_IOожидания в соответствии с обоими sys.dm_exec_session_wait_statsи 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 использует удивительное количество ЦП во время цикла (в среднем около половины ядра). Я не могу понять, что SSMS делает за это время.

Почему простой цикл вызывает ASYNC_NETWORK_IOожидания при выполнении через SSMS? Единственный вывод, который я получаю от клиента при выполнении этого запроса, - это «Команды выполнены успешно». сообщение.

Джо Оббиш
источник

Ответы:

31

Документация для SET NOCOUNTговорит:

SET NOCOUNT ONпредотвращает отправку DONE_IN_PROCсообщений клиенту для каждого оператора в хранимой процедуре. Для хранимых процедур , которые содержат несколько заявлений , которые не возвращают много фактических данных, а также для процедур , которые содержат циклы Transact-SQL, настройки , SET NOCOUNTчтобы ONможет обеспечить значительный прирост производительности, поскольку сетевой трафик значительно снижаются.

Вы не выполняете операторы в хранимой процедуре, поэтому SQL Server отправляет DONEтокены (код 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начинается ожидание:

отправка пакета

Пример пакета 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 работал в течение примерно 4 секунд с 200-300 мс ожидания. sqlcmdпробежал 2-3 секунды с ожиданием одной цифры мс; osqlпримерно в то же время выполнения с примерно 10 мс ожидания.

Самым худшим клиентом для этого теста была Azure Data Studio. Он работал почти 6 часов:

ОБЪЯВЛЕНИЯ

Пол Уайт говорит, что GoFundMonica
источник