22.6.26

Выявление запросов с ожиданиями SOS_SCHEDULER_YIELD

Автор: Paul Randal, Identifying queries with SOS_SCHEDULER_YIELD waits

Одна из проблем с типом ожидания SOS_SCHEDULER_YIELD заключается в том, что это на самом деле не тип ожидания. Когда возникает этот тип ожидания, это происходит потому, что поток исчерпал свой 4-миллисекундный квант планирования и добровольно уступил ЦП, перейдя непосредственно в конец очереди выполнения (Runnable Queue) для планировщика, минуя список ожидания (Waiter List). Однако ожидание должно быть зарегистрировано, когда поток покидает процессор, поэтому используется SOS_SCHEDULER_YIELD.

Эти ожидания стоит исследовать, если они преобладают на вашем сервере, так как они могут указывать на выполнение больших сканирований данных, которые уже находятся в памяти, в то время как вы бы предпочли маленькие поиски по индексу.

Проблема в том, что они не являются настоящим типом ожидания, поэтому вы не можете использовать мой скрипт для просмотра sys.dm_os_waiting_tasks и получения планов запросов потоков, вызывающих этот тип ожидания, потому что эти потоки не ожидают ресурса и поэтому не отображаются в выводе sys.dm_os_waiting_tasks!

Решение — использовать динамическое административное представление sys.dm_exec_requests, так как оно показывает last_wait_type для всех выполняющихся запросов. Ниже приведён скрипт, который можно использовать.

SELECT [er].[session_id], [es].[program_name], [est].text, [er].[database_id], [eqp].[query_plan], [er].[cpu_time] FROM sys.dm_exec_requests [er] INNER JOIN sys.dm_exec_sessions [es] ON [es].[session_id] = [er].[session_id] OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est] OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp] WHERE [es].[is_user_process] = 1 AND [er].[last_Wait_type] = N'SOS_SCHEDULER_YIELD' ORDER BY [er].[session_id]; GO

Этот скрипт покажет вам код и план запроса того, что происходит, но даже с этим может быть неочевидно, какой именно оператор вызывает это ожидание, поэтому вам может потребоваться прибегнуть к захвату стеков вызовов SQL Server, как я объясняю в ссылке на блог выше.

Описание типа ожидания SOS_SCHEDULER_YIELD

Этот тип ожидания возникает, когда поток смог выполниться в течение полного кванта времени (4 миллисекунды во всех версиях SQL Server, неизменяемо) и поэтому добровольно уступил планировщик, переместившись в конец очереди выполнения (Runnable Queue) своего планировщика. Хотя поток сразу переходит в состояние RUNNABLE, он не попадает в список ожидания (Waiter List), потому что ему не нужно ожидать ресурса. Несмотря на то, что потоку не нужно ждать, он должен зарегистрировать тип ожидания при переключении контекста с процессора, и этим типом является SOS_SCHEDULER_YIELD.

(Описание из Books Online: «Возникает, когда задача добровольно уступает планировщик для выполнения других задач. Во время этого ожидания задача ждёт возобновления своего кванта.»)

Дополнительная информация

Существуют различные шаблонные реакции на этот тип ожидания:

  1. «Должно быть, проблема в спинблокировках» — нет, спинблокировки не отслеживаются типами ожиданий.
  2. «Должно быть давление на ЦП» — нет, давление на ЦП указывается растущим временем сигнального ожидания (signal-wait times) и длинными очередями выполнения, а не распространённостью ожиданий SOS_SCHEDULER_YIELD.
  3. «Запросу нужно больше ЦП» — нет, см. пункт 2.

Наиболее распространённая причина ожиданий SOS_SCHEDULER_YIELD, которую я вижу, — это запросы, выполняющие сканирование страниц, которые находятся в памяти и не изменяются, следовательно, нет конкуренции за доступ к страницам, и поток сканирования может выполняться, пока не исчерпает свой квант времени. Это может быть связано с тем, что план запроса ошибочно выполняет сканирование таблицы, или это может быть нормальной частью вашей рабочей нагрузки. Как и в случае с ожиданиями CXPACKET, не делайте поспешного вывода, что ожидания SOS_SCHEDULER_YIELD — это плохо.

Я написал большую статью о понимании и устранении ожиданий SOS_SCHEDULER_YIELD на sqlperformance.com, которая более подробно объясняет планирование потоков и исчерпание квантов, а также устранение неполадок. В основном это включает идентификацию запроса, который вызывает ожидания SOS_SCHEDULER_YIELD, и проверку того, что план запроса выглядит корректным (например, отсутствует ли некластерный индекс, вызывающий сканирование таблицы в памяти?). Обратите внимание, что запросы, вызывающие ожидания SOS_SCHEDULER_YIELD, не отображаются в sys.dm_os_waiting_tasks, поэтому вам нужен скрипт, который использует sys.dm_exec_requests — и такой скрипт есть в этой статье выше.

Когда квант времени потока истекает, поток обязан уступить процессор. Он не имеет информации о других потоках на этом планировщике, и всегда происходит переключение контекста, когда поток переходит в конец очереди выполнения, даже если он единственный поток на планировщике. Поток не может решить просто не уступать. Именно переключение контекста заставляет регистрировать тип ожидания внутри SQLOS. Если переключение контекста не происходит (потому что поток не проверяет, истёк ли квант), это невыполняющий уступку планировщик (non-yielding scheduler), и вы увидите сообщение 17883 в журнале ошибок.

Ожидания SOS_SCHEDULER_YIELD всегда имеют нулевой компонент ожидания ресурса (0 resource wait component), потому что ожидания ресурса не происходит (именно поэтому поток не попадает в список ожидания).

Ещё одна вещь, которую следует учитывать, — работает ли ваша рабочая нагрузка на виртуальной машине, которая испытывает задержки из-за переподписки хоста. Это может повысить количество ожиданий SOS_SCHEDULER_YIELD, как описано в этой статье.

Наконец, некоторые рабочие нагрузки могут страдать от ожиданий SOS_SCHEDULER_YIELD, когда включена автоматическая программная NUMA (soft-NUMA) — подробности см. в этой статье.

Известные случаи в SQL Server

(Номера в списке соответствуют списку стеков вызовов ниже)

  1. Уступка во время сканирования таблицы (в данном случае как часть упорядоченного параллельного сканирования таблицы).
  2. Уступка во время выполнения сортировки (в данном случае как часть выполнения соединения вложенными циклами).
  3. Уступка во время сканирования значений LRU буферов в буферном пуле для заполнения списка свободных буферов (в данном случае при выделении страницы в индексе как часть разбиения страницы).
  4. Уступка во время вычисления оценок кардинальности при компиляции плана запроса.
  5. Уступка во время сканирования списка буферов для базы данных (в данном случае при завершении работы базы данных как часть DROP DATABASE).
  6. И многие, многие другие подобные стеки вызовов из всех частей SQL Server.

Сокращённые стеки вызовов

(Номера в списке соответствуют номерам известных случаев)

  1. SOS_Task::PostWait+90
    SOS_Task::Sleep+147
    IndexPageManager::GetNextPage+33b
    IndexRowScanner::MoveKeyOrderToRowOnNextPage+16c
    IndexRowScanner::MoveToRowOnNextPage+23b
    IndexDataSetSession::GetNextRowValuesInternal+105b
    RowsetNewSS::FetchNextRow+197
    CQScanTableScanNew::GetRow+f2
    CQScanXProducerNew::GetRowHelper+366
    CQScanXProducerNew::GetRow+15
    FnProducerOpen+57
    FnProducerThread+851
    SubprocEntrypoint+a59
    SOS_Task::Param::Execute+21e
    SOS_Scheduler::RunTask+a8
  2. SOS_Task::PostWait+90
    SOS_Task::Sleep+147
    YieldAndCheckForAbort+c3
    lmAddCurToList+1d1
    lmlink+12c7
    soAllocRecBuf+328
    RowsetSorted::InsertRow+2b97
    RowsetChangeSort::InsertRow+19
    CValRowNoHrow::SetDataX+48
    CQScanSortNew::PushRow+34
    CQScanSortNew::BuildSortTable+28f
    CQScanSortNew::OpenHelper+c0
    CQScanNLJoinNew::Open+24
    CQScanNLJoinNew::Open+24
    CQScanNLJoinNew::Open+24
    CQScanNLJoinNew::Open+24
    CQScanNew::OpenHelper+41
    CQScanTopNew::Open+15
    CQueryScan::StartupQuery+240
    CXStmtQuery::SetupQueryScanAndExpression+2bd
    CXStmtQuery::InitForExecute+34
  3. SOS_Task::PostWait+90
    SOS_Task::Sleep+1b2
    Worker::OSYieldNoAbort+2f
    BPool::ReplenishFreeList+561
    BPool::Steal+52f
    BPool::NewPage+7af
    PageRef::SetupPageHeaderPreAllocation+64
    SetupPageHeaderPreAllocation+6c
    TargetExtentMgr::AllocPageFromTargetExtent+5cd
    AllocationReq::AllocateFromExistingExtents+9e1
    AllocationReq::AllocatePages+15ca
    AllocationReq::Allocate+f3
    AllocateHoBtDataPage+5fd
    IndexPageManager::AllocatePage+1b8
    SplitPage+b59
    IndexDataSetSession::InsertSmallRecord+1b5c
    IndexDataSetSession::InsertRowInternal+2d11
    DatasetSession::InsertRow+163
    RowsetNewSS::InsertRow+26
    CBlobHandleFactoryMain::CreateNewBlobHandleInternal+1ec
    CBlobHandleFactoryMain::CreateNewBlobHandle+88
    CBlobHandleHelper::CopyBlobHandleIntoTempOrInlined+1c5
  4. SOS_Task::PostWait+90
    SOS_Task::Sleep+ea
    YieldAndCheckForAbort+ec
    BuildDensityMap+26
    OptimizerUtil::CalculateDistinctCardUsingDensity+1da
    OptimizerUtil::GroupCardDistinctHelper+8d2
    CCostUtils::CalcLoopJoinCachedInfo+2036
    CCostUtils::PcctxLoopJoinHelper+124
    CTask_OptInputs::Perform+680
    CMemo::ExecuteTasks+17c
    CMemo::PerformOptimizationStage+347
    CMemo::OptimizeQuery+9db
    COptContext::PexprSearchPlan+131
    COptContext::PcxteOptimizeQuery+24b8
    COptContext::PqteOptimizeWrapper+221
    PqoBuild+db6
    CStmtQuery::InitQuery+27f
    CStmtSelect::Init+ee
    CCompPlan::FCompileStep+1844
    CSQLSource::FCompile+13f7
    CSQLSource::FCompWrapper+d3
    CSQLSource::Transform+445
  5. SOS_Task::PostWait+90
    SOS_Task::Sleep+1b2
    Worker::OSYieldNoAbort+2f
    BufIter::GetNext+100
    BPool::RemoveDatabaseByFileId+c6
    RecoveryUnit::Shutdown+14c
    DBTABLE::Shutdown+179
    DBMgr::ShutdownDB+1b1
    NotifyEndDropDatabase::HandleEvent+69
    XactRM::FireNotificationsInternal+cf
    XactRM::SinglePhaseCommit+558
    XactRM::CommitInternal+4b9
    FullXactImp::Commit+326
    CMsqlXactInternalReadWrite::Commit+15
    CMsqlXactImp::Commit+1d2
    CAutoMsqlXact::CommitNestedXact+bc
    CStmtDropDB::XretExecute+bb1
    CMsqlExecContext::ExecuteStmts<0,1>+864
    CMsqlExecContext::FExecute+a48
    CSQLSource::Execute+866

21.6.26

Советы и хитрости для высокой производительности FILESTREAM

Автор: Paul Randal, High-performance FILESTREAM tips and tricks

У меня было много вопросов о производительности FILESTREAM и о том, как заставить NTFS хорошо масштабироваться. Я только что закончил писать 30-страничный технический документ о FILESTREAM для команды SQL Server, который должен быть опубликован до конференции PASS 2008 в ноябре. Хотя мой технический документ не совсем о производительности, в нём есть длинный раздел о настройке системы для достижения высокой производительности FILESTREAM. В этой статье я хочу дать список рекомендаций, которые помогут вам добиться хорошей производительности. Все они более подробно описаны в техническом документе.

20.6.26

Могут ли ключи кластерного индекса с типом GUID вызывать фрагментацию некластерных индексов?

Автор: Paul Randal, Can GUID cluster keys cause non-clustered index fragmentation?

На встрече пользовательской группы я потратил некоторое время на объяснение того, как GUID могут вызывать фрагментацию как в кластерных, так и в некластерных индексах, даже если GUID специально не включён в ключ некластерного индекса. GUID — это, по сути, случайные значения (псевдослучайные в диапазонах, если генерируются с помощью NEWSEQUENTIALID), которые также уникальны. Их уникальность делает их привлекательными для многих разработчиков в качестве значения ключа, не понимая при этом того хаоса, который они могут вызвать в производственной среде с точки зрения фрагментации и низкой производительности запросов.

19.6.26

Насколько сложно выбрать правильные некластерные индексы?

Автор: Paul Randal, How hard is it to pick the right non-clustered indexes?

На собрании группы разработчиков .NET в Редмонде, и во время того, как Кимберли рассказывала о пропущенных и лишних индексах, возник следующий вопрос:

«Какой некластерный индекс лучше всего использовать для запроса с условием WHERE lastname = 'Randal' AND firstname = 'Paul' AND middleinitial = 'S'

Кимберли сказала, что для этого случая порядок ключей не имеет значения. Я подумал секунду, а затем возразил, сказав, что наиболее селективный столбец должен быть первым. Мы согласились обсудить это с группой в конце, но я подумал ещё немного и понял (и признался группе), что она права – мне следовало бы знать, что не стоит подвергать сомнению знания Кимберли об индексировании… :-)

18.6.26

Разочарование от обобщений

Автор: Paul Randal, The frustration of sweeping generalizations – follow on from Search Engine Q&A #12

Постоянное участие в дискуссиях побудило меня высказать свои мысли о многочисленных обобщениях, которые преследуют индустрию и иногда затрудняют дачу советов на форумах и в блогах. 

Некоторые примеры вопросов, которые порождают обобщения:

  • Следует ли создавать кластерные индексы для всех таблиц? (Известный спор о кластерных индексах, как его любит называть Кимберли.)
  • Следует ли перестраивать или реорганизовать индексы для устранения фрагментации?
  • Какое решение высокой доступности следует использовать?

Проблема — как и с большинством советов — в том, что крайне сложно делать обобщения. Это связано как с тем, что без большого количества доказательств многие люди (вполне обоснованно) не верят обобщениям, поскольку они могли обжечься на одном из них в прошлом, так и с тем, что почти каждая ситуация уникальна, поэтому многие обобщения бесполезны.

Накопительный пакет обновления SQL Server 2025 CU6 - KB5093421

Описание: KB5093421

Скачать: SQLServer2025-KB5093421-x64.exe

Дата выпуска: 17 июня 2026 г.

SQL Server 2025 — Версия: 17.0.4055.5


17.6.26

Журнал транзакций SQL Server. Часть 4: записи журнала

Автор: Paul Randal, The SQL Server Transaction Log, Part 4: Log Records

Ранее я рассказывал о том, зачем нужно ведение журнала, об архитектуре и циклической природе журнала, и теперь пришло время взглянуть на самое сердце системы ведения журнала — на сами записи журнала. Как всегда, рекомендую прочитать предыдущие статьи в этой серии, прежде чем читать эту.

16.6.26

Следует ли создавать несколько файлов для пользовательской базы данных на многопроцессорном сервере?

Автор: Paul Randal, Search Engine Q&A #12: Should you create multiple files for a user DB on a multi-core box?

На сайте SQLServerCentral.com развернулась очень интересная дискуссия о том, стоит ли создавать несколько файлов для пользовательской базы данных, потому что на сервере несколько ЦП. Я написал пару длинных ответов в ходе дискуссии и хотел продублировать их здесь, так как считаю, что это представляет широкий интерес.

15.6.26

Горячее добавление ЦП и маска привязки

Автор: Paul Randal, SQL Server 2008: Hot-Add CPU (and affinity masks)

Короткая заметка сегодня, так как я готовлюсь к выступлению на собрании пользовательской группы SQL Server в Тихоокеанском Северо-Западе сегодня вечером в кампусе Microsoft в Редмонде.

SQL Server 2005 представил концепцию горячего добавления памяти (hot-add memory) для динамической обработки рабочей нагрузки. SQL Server 2008 расширяет эти возможности, добавляя также горячее добавление ЦП (hot-add CPU). Начиная с SQL Server 2025 (17.x), функция горячего добавления ЦП не рекомендуется и планируется удалить в будущей версии SQL Server. «Горячее добавление» означает возможность установить ЦП в работающую машину и затем перенастроить SQL Server для использования этого ЦП ONLINE (т.е. без какого-либо простоя приложения).

14.6.26

И вот однажды RCSI сделал результаты запросов более точными


Автор: Brent Ozar, And Then There Was The Time RCSI Actually Made Query Results More Accurate

Обычно, когда я рассказываю людям об оптимистичных уровнях изоляции SQL Server — Read Committed Snapshot Isolation (RCSI) и Snapshot Isolation (SI) — мне приходится произносить небольшую речь о том, что им нужно тестировать свои запросы, потому что результаты могут измениться.

Однако недавно я работал с клиентом, который получал неверные результаты запросов при использовании пессимистичного уровня изоляции по умолчанию — и мы переключились на RCSI, чтобы это исправить! Я не буду объяснять здесь RCSI или SI — используйте ссылку выше для ознакомления с основами — вместо этого я сосредоточусь на демонстрационном скрипте, который я написал, чтобы показать проблему, с которой они столкнулись, и то, как RCSI её решил.

7.6.26

Новое в SQL Server 2025: функции кодирования и декодирования Base64

Автор: Leonard Lobel, Base64 Encoding and Decoding in SQL Server 2025 and Azure SQL Database

SQL Server 2025 добавляет встроенную поддержку кодирования и декодирования Base64 с помощью двух T-SQL-функций: BASE64_ENCODE и BASE64_DECODE. Эти функции значительно упрощают преобразование двоичных данных в дружественные к тексту представления и обратное преобразование строк в двоичные данные, когда это необходимо.

Это полезно во многих повседневных сценариях: встраивание двоичного содержимого в JSON, создание URL данных для HTML, передача двоичных полезных нагрузок через текстовые протоколы и создание безопасных для URL токенов. Раньше разработчикам часто приходилось полагаться на XML-трюки, код на стороне приложения, CLR-функции или собственную логику преобразования. Теперь эта функциональность доступна непосредственно в T-SQL.

Важно: Base64 — это формат кодирования, а не механизм шифрования. Он делает двоичные данные дружественными к тексту, но не обеспечивает безопасность или скрытие основных данных.

6.6.26

Как влияет сжатие резервных копий на загрузку процессоров

Автор: Paul Randal, SQL Server 2008: Backup Compression CPU Cost

Я давно обещал написать о встроенном сжатии резервных копий (Backup Compression). Для этой статьи я расширил базу данных AdventureWorks до 322 МБ (случайный размер, но достаточно большой, чтобы получить приемлемое время выполнения на моём сервере). Я использовал системный монитор (System Monitor) для измерения времени ЦП в пользовательском режиме (%user-mode CPU time), а также пропускной способности резервного копирования и восстановления для сжатой и несжатой операций резервного копирования, а затем и восстановления.