Автор: Paul Randal, Knee-Jerk Wait Statistics : SOS_SCHEDULER_YIELD
В этой статье я продолжу тему статистики ожиданий и расскажу об ожидании SOS_SCHEDULER_YIELD.
Когда SOS_SCHEDULER_YIELD является преобладающим на сервере, часто наблюдается устойчивое высокое использование ЦП. Шаблонная реакция здесь заключается в том, что сервер, должно быть, испытывает давление на ЦП или что проблема в спинблокировке.
Нам нужно немного предыстории, чтобы понять эти две реакции.
Планирование потоков
Планирование потоков в SQL Server управляется самим SQL Server, а не Windows (то есть оно не вытесняющее). Часть Storage Engine, называемая SQLOS, обеспечивает функциональность планирования, и потоки переходят от выполнения на процессоре (состояние RUNNING) к нахождению в списке ожидания (Waiter List) в ожидании ресурса (состояние SUSPENDED), затем в очередь выполнения (Runnable Queue) после того, как ресурс становится доступным (состояние RUNNABLE), ожидая, пока они достигнут вершины очереди и снова попадут на процессор (возвращаясь в состояние RUNNING).
Всякий раз, когда потоку требуется ресурс, который он не может немедленно получить, он переходит в состояние ожидания и находится в списке ожидания, пока ему не сообщат (сигнализируют), что его ресурс доступен. Время, проведённое в списке ожидания, — это время ожидания ресурса (resource wait time), а время, проведённое в очереди выполнения, — это время сигнального ожидания (signal wait time). Вместе они составляют общее время ожидания. SQLOS отслеживает время ожидания и время сигнального ожидания, поэтому нам нужно выполнить некоторые вычисления на основе вывода sys.dm_os_wait_stats, чтобы получить время ожидания ресурса (например, с помощью моего скрипта).
Список ожидания неупорядочен (любой поток в нём может быть сигнализирован в любой момент и перемещён в очередь выполнения), а очередь выполнения почти всегда работает по принципу «первым пришёл — первым обслужен» (FIFO). Единственное исключение — когда несколько групп рабочей нагрузки Resource Governor настроены в одном пуле ресурсов и имеют разные приоритеты по отношению друг к другу. Я никогда не видел, чтобы это успешно использовалось в производстве, поэтому не буду обсуждать это дальше.
Существует ещё одна причина, по которой поток может быть вынужден покинуть процессор — он исчерпывает свой квант. Квант времени потока в SQLOS фиксирован и составляет 4 миллисекунды. Сам поток отвечает за определение того, что его квант исчерпан (путём вызова вспомогательных процедур в SQLOS), и добровольно уступает процессор (это называется уступкой, yielding). Когда это происходит, поток перемещается непосредственно в конец очереди выполнения, так как ему нечего ждать. Однако SQLOS должна зарегистрировать тип ожидания для этого перехода с процессора и регистрирует SOS_SCHEDULER_YIELD.
Это поведение часто ошибочно принимают за давление на ЦП, но это не так — это просто устойчивое использование ЦП. Давление на ЦП и его распознавание — это совсем другая тема для будущей статьи. Насколько касается этой статьи, пока среднее время сигнального ожидания низкое (0–0.1–0.2 мс), можно с большой вероятностью предположить, что давление на ЦП не является проблемой.
Спинблокировки
Спинблокировка (spinlock) — это примитив синхронизации очень низкого уровня, который используется для обеспечения потокобезопасного доступа к структурам данных в SQL Server, которые являются чрезвычайно «горячими» (очень изменчивыми и чрезвычайно часто изменяемыми несколькими потоками). Примерами таких структур являются список свободных буферов в каждой части буферного пула и массив весов пропорционального заполнения для файлов данных в файловой группе.
Когда потоку нужно захватить спинблокировку, он проверяет, свободна ли она, и если да, то немедленно захватывает её. Если спинблокировка не может быть захвачена, поток немедленно пытается захватить её снова, и снова, и снова, до тысячи итераций, пока не отступит (не заснёт на некоторое время). Это не регистрируется как какой-либо тип ожидания, так как поток просто вызывает функцию sleep() Windows, но это может привести к тому, что другие ожидающие потоки будут иметь большие (10–20+ мс) времена сигнального ожидания, так как спящий поток остаётся на процессоре, пока не получит спинблокировку.
Почему я говорю о спинблокировках? Потому что они также могут быть причиной высокого использования ЦП, и существует заблуждение, что спинблокировки являются причиной ожиданий SOS_SCHEDULER_YIELD. Это не так.
Причины SOS_SCHEDULER_YIELD
Таким образом, существует одна причина для SOS_SCHEDULER_YIELD: поток, исчерпывающий свой квант планирования. Часто повторяющиеся случаи могут привести к тому, что SOS_SCHEDULER_YIELD станет преобладающим ожиданием вместе с высоким использованием ЦП.
Вы не увидите ожидания SOS_SCHEDULER_YIELD в выводе sys.dm_os_waiting_tasks, так как поток не ожидает. Вы можете увидеть, какой запрос генерирует ожидания SOS_SCHEDULER_YIELD, запросив sys.dm_exec_requests и отфильтровав по столбцу last_wait_type.
Это также означает, что когда вы видите SOS_SCHEDULER_YIELD в выводе sys.dm_os_wait_stats, время ожидания ресурса будет равно нулю, потому что фактического ожидания не было. Но помните, что каждое из этих «ожиданий» равно 4 мс времени ЦП, накопленного для запроса.
Единственный способ доказать, что вызывает ожидания SOS_SCHEDULER_YIELD, — это захватить стеки вызовов SQL Server, когда возникает этот тип ожидания, используя расширенные события (Extended Events) и отладочные символы от Microsoft. У меня есть статья в блоге, в которой описывается и показывается, как провести такое исследование. Вот ещё две статьи о спинлоках, которые помогут пониманию их работы: Диагностика и устранение конфликтов спинлока в SQL Server и Диагностика и устранение конфликтов кратковременных блокировок на SQL Server.
В случае исчерпания кванта это не является первопричиной. Это дальнейший симптом. Теперь нам нужно рассмотреть, почему поток может исчерпывать свой квант неоднократно.
Поток может исчерпать свой квант только тогда, когда он может продолжать обработку кода SQL Server в течение 4 мс без необходимости в ресурсе, который принадлежит другому потоку — без ожидания блокировок, кратких блокировок страниц (page latches), чтения страниц файлов данных с диска, выделения памяти, роста файлов, ведения журнала или множества других ресурсов, которые могут потребоваться потоку.
Наиболее распространённый фрагмент кода, где может происходить исчерпание кванта и накапливаться большое количество ожиданий SOS_SCHEDULER_YIELD, — это сканирование индекса/таблицы, где все необходимые страницы файлов данных находятся в памяти и нет конкуренции за доступ к этим страницам. Именно поэтому я рекомендую искать большие и/или повторяющиеся сканирования индекса/таблицы в планах запросов, когда вы видите SOS_SCHEDULER_YIELD как основной тип ожидания.
Это не означает, что я говорю, что большие сканирования — это плохо, так как может быть, что наиболее эффективный способ обработки вашей рабочей нагрузки — это сканирование. Однако если ожидания SOS_SCHEDULER_YIELD новы и необычны и вызваны большими сканированиями, вам следует исследовать, почему планы запросов используют сканирования. Возможно, кто-то удалил критический некластерный индекс, или статистика устарела, и поэтому был выбран неправильный план запроса, или, возможно, необычное значение параметра было передано хранимой процедуре, и план запроса потребовал сканирования, или произошло изменение кода без добавления поддерживающих индексов.
Резюме
Как и в случае с другими типами ожиданий, понимание того, что именно означает SOS_SCHEDULER_YIELD, является ключом к пониманию того, как его устранять и является ли поведение ожидаемым из-за обрабатываемой рабочей нагрузки.
Что касается общей статистики ожиданий, вы можете найти больше информации об их использовании для устранения неполадок производительности в:
- Моей серии статей в блоге SQLskills, начиная с Wait statistics, or please tell me where it hurts.
- Моей библиотеке типов ожиданий и классов кратких блокировок.

Комментариев нет:
Отправить комментарий