Оптимизация SQL Server по CPI вместе с ИИ

Время чтения - 18 мин.Дата публикации 21.09.2026

Runbook: Диагностика высокой загрузки CPU на SQL Server

Пошаговое руководство по сбору данных, анализу ожиданий, поиску проблемных запросов и типовым решениям для Microsoft SQL Server. Скрипты PowerShell и T-SQL готовы к запуску.

Когда использовать: SQL Server тормозит, CPU загружен постоянно или импульсно, сервер не отвечает, приложения жалуются на медленную работу.


Шаг 0. Быстрая триаж-сводка (PowerShell)

Соберите «пульс» сервера одной пачкой. Если что-то уже кричит — увидите сразу. Запускать от имени администратора.

# Сохранить всё в одну папку
$outDir = "C:\Diag_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
New-Item -Path $outDir -ItemType Directory | Out-Null
Write-Host "Папка для выгрузок: $outDir"

# --- 1. Общая загрузка CPU и топ процессов ---
Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 5 |
    Export-Counter -Path "$outDir\cpu_total.blg" -Force

Get-Process | Sort-Object CPU -Descending | Select-Object -First 15 `
    Name, Id, CPU, @{N='WS_MB';E={[math]::Round($_.WorkingSet64/1MB,1)}}, Path |
    Format-Table -AutoSize | Out-File "$outDir\top_processes.txt"

# --- 2. Память ---
Get-CimInstance Win32_OperatingSystem |
    Select-Object TotalVisibleMemorySize, FreePhysicalMemory, TotalVirtualMemorySize, FreeVirtualMemory |
    Format-List | Out-File "$outDir\memory.txt"

# --- 3. Службы SQL Server ---
Get-Service | Where-Object { $_.Name -like 'MSSQL*' -or $_.Name -like 'SQLAgent*' } |
    Select-Object Name, DisplayName, Status, StartType |
    Format-Table -AutoSize | Out-File "$outDir\sql_services.txt"

# --- 4. Версия SQL Server (через реестр) ---
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\*\MSSQLServer\CurrentVersion' -ErrorAction SilentlyContinue |
    Select-Object PSChildName, CurrentVersion, ProductName |
    Format-List | Out-File "$outDir\sql_version.txt"

Write-Host "Готово. Выгрузки в $outDir"

Куда смотреть

  • % Processor Time — если стабильно выше 70–80%, идём дальше.
  • top_processes.txt — если sqlservr.exe в топе, дальше по SQL. Если другой процесс — разбираемся с ним.
  • memory.txt — если FreePhysicalMemory меньше 500 МБ, проблема в нехватке RAM.
  • sql_version.txt — если версия RTM (например, 16.0.1000.6), срочно нужен CU.

Шаг 1. Внутренняя диагностика SQL Server

1.1. Общая статистика CPU из Ring Buffer (последние 10 минут)

Показывает историю загрузки CPU как для SQL Server, так и для всей системы. Хорошо для быстрой оценки текущей ситуации.

WITH ScheduleMonitorResults AS (
    SELECT 
        DATEADD(ms, (SELECT [ms_ticks] - [timestamp] FROM sys.dm_os_sys_info), GETDATE()) AS EventDateTime,
        CAST(record AS XML) AS record
    FROM sys.dm_os_ring_buffers
    WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
      AND [timestamp] > (SELECT [ms_ticks] - 10*60000 FROM sys.dm_os_sys_info)
)
SELECT 
    CONVERT(varchar, EventDateTime, 126) AS EventTime,
    SysHealth.value('ProcessUtilization[1]','int') AS [CPU_SQL_%],
    100 - SysHealth.value('SystemIdle[1]','int') AS [CPU_All_%]
FROM ScheduleMonitorResults
CROSS APPLY record.nodes('/Record/SchedulerMonitorEvent/SystemHealth') T(SysHealth)
ORDER BY EventDateTime DESC;

1.2. Ожидания (Wait Stats) — топ-15

Важно: на «свежем» сервере статистика репрезентативна. Если сервер работает давно — значения накопленные. Сбрасывать только осознанно и не в рабочее время:

DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR);
SELECT TOP 15
    wait_type,
    wait_time_ms / 1000.0 AS wait_sec,
    waiting_tasks_count,
    wait_time_ms / NULLIF(waiting_tasks_count, 0) AS avg_wait_ms,
    signal_wait_time_ms / 1000.0 AS signal_wait_sec
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
    'CLR_SEMAPHORE','LAZYWRITER_SLEEP','RESOURCE_QUEUE','SLEEP_TASK',
    'SLEEP_SYSTEMTASK','SQLTRACE_BUFFER_FLUSH','WAITFOR','LOGMGR_QUEUE',
    'CHECKPOINT_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT',
    'BROKER_TO_FLUSH','BROKER_TASK_STOP','CLR_MANUAL_EVENT','CLR_AUTO_EVENT',
    'DISPATCHER_QUEUE_SEMAPHORE','FT_IFTS_SCHEDULER_IDLE_WAIT','XE_DISPATCHER_WAIT',
    'XE_DISPATCHER_JOIN','SQLTRACE_INCREMENTAL_FLUSH_SLEEP','ONDEMAND_TASK_QUEUE',
    'BROKER_EVENTHANDLER','SLEEP_BPOOL_FLUSH','BROKER_RECEIVE_WAITFOR',
    'DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','SP_SERVER_DIAGNOSTICS_SLEEP',
    'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','QDS_ASYNC_QUEUE','QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'
)
ORDER BY wait_time_ms DESC;

Интерпретация ожиданий

Wait type Что означает Действие
SOS_SCHEDULER_YIELD CPU-голодание, запросы ждут процессор Искать топ-запросы по CPU (Шаг 1.4)
CXPACKET / CXCONSUMER Параллелизм Проверить MAXDOP, Cost Threshold for Parallelism
PAGEIOLATCH_* Ждём чтения с диска Проверить память и индексы
RESOURCE_SEMAPHORE Ждём memory grant Давление на память
RESOURCE_SEMAPHORE_QUERY_COMPILE Много компиляций Проблема с параметризацией
LCK_* Блокировки Искать блокирующие сессии
WRITELOG Медленный лог Проверить диск под лог
PREEMPTIVE_OS_QUERYREGISTRY Баг SQL 2022 RTM Ставить CU5+ и TF 12502

1.3. Кто прямо сейчас ест CPU (активные запросы)

Запускать в момент всплеска нагрузки. Показывает выполняющиеся сейчас запросы и уже потраченный ими CPU.

SELECT TOP 10
    r.session_id,
    r.start_time,
    r.status,
    r.command,
    r.cpu_time AS cpu_ms,
    r.total_elapsed_time AS elapsed_ms,
    r.wait_type,
    r.blocking_session_id,
    DB_NAME(r.database_id) AS db_name,
    SUBSTRING(t.text, (r.statement_start_offset/2)+1,
        ((CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(t.text)
          ELSE r.statement_end_offset END - r.statement_start_offset)/2)+1) AS query_text,
    qp.query_plan
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) qp
WHERE r.session_id > 50
ORDER BY r.cpu_time DESC;

1.4. Топ-запросов по накопленному CPU (исторический анализ)

Анализирует кэш планов. Показывает, какие запросы потребляли больше всего CPU с момента последнего перезапуска SQL Server или очистки кэша.

SELECT TOP 10
    qs.total_worker_time/1000 AS total_cpu_ms,
    qs.execution_count,
    qs.total_worker_time/NULLIF(qs.execution_count,0)/1000 AS avg_cpu_ms,
    qs.total_elapsed_time/1000 AS total_elapsed_ms,
    qs.total_logical_reads,
    qs.total_physical_reads,
    SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
          ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text,
    qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY qs.total_worker_time DESC;

Если запрос вернул пусто — нет прав VIEW SERVER STATE или кэш планов очищен.

1.5. Память SQL Server (внутренняя)

-- Внешняя картина
SELECT 
    total_physical_memory_kb/1024 AS total_phys_mb,
    available_physical_memory_kb/1024 AS avail_phys_mb,
    system_memory_state_desc
FROM sys.dm_os_sys_memory;

-- Что SQL держит
SELECT 
    physical_memory_in_use_kb/1024 AS sql_phys_mb,
    virtual_address_space_committed_kb/1024 AS sql_vm_committed_mb,
    page_fault_count
FROM sys.dm_os_process_memory;

-- Разбивка по клеркам
SELECT TOP 10 
    type,
    SUM(pages_kb) AS pages_kb,
    SUM(virtual_memory_committed_kb) AS vm_committed_kb
FROM sys.dm_os_memory_clerks
GROUP BY type
ORDER BY pages_kb DESC;

1.6. Настройки SQL Server

SELECT name, value, value_in_use, description
FROM sys.configurations
WHERE name IN (
    'max server memory (MB)',
    'min server memory (MB)',
    'cost threshold for parallelism',
    'max degree of parallelism',
    'optimize for ad hoc workloads',
    'query governor cost limit'
)
ORDER BY name;

-- Флаги трассировки
DBCC TRACESTATUS(-1);

1.7. Активные задания SQL Agent

Помогает понять, не совпадают ли всплески CPU с расписанием заданий (сбор статистики, индексация, бэкапы).

SELECT 
    j.name AS job_name,
    ja.start_execution_date,
    ja.stop_execution_date,
    DATEDIFF(SECOND, ja.start_execution_date, GETDATE()) AS running_sec,
    ja.last_executed_step_id
FROM msdb.dbo.sysjobactivity ja
JOIN msdb.dbo.sysjobs j ON ja.job_id = j.job_id
WHERE ja.session_id = (SELECT MAX(session_id) FROM msdb.dbo.sysjobactivity)
  AND ja.start_execution_date IS NOT NULL
  AND ja.stop_execution_date IS NULL;

1.8. Блокировки и deadlocks

-- Текущие блокировки
SELECT 
    blocking.session_id AS blocker,
    blocked.session_id AS blocked,
    blocking.wait_type,
    blocking.wait_time/1000 AS wait_sec,
    blocking_text.text AS blocker_sql,
    blocked_text.text AS blocked_sql
FROM sys.dm_exec_requests blocked
JOIN sys.dm_exec_requests blocking ON blocked.blocking_session_id = blocking.session_id
CROSS APPLY sys.dm_exec_sql_text(blocking.sql_handle) blocking_text
CROSS APPLY sys.dm_exec_sql_text(blocked.sql_handle) blocked_text
WHERE blocked.blocking_session_id > 0;

-- Открытые транзакции
SELECT 
    s.session_id,
    s.login_name,
    s.host_name,
    s.program_name,
    t.transaction_begin_time,
    DATEDIFF(SECOND, t.transaction_begin_time, GETDATE()) AS open_sec,
    t.transaction_type,
    t.transaction_state
FROM sys.dm_tran_active_transactions t
JOIN sys.dm_tran_session_transactions st ON t.transaction_id = st.transaction_id
JOIN sys.dm_exec_sessions s ON st.session_id = s.session_id
WHERE s.session_id > 50
ORDER BY t.transaction_begin_time;

1.9. Кольцевой буфер: ошибки и OOM

-- Ошибки, связанные с памятью
SELECT 
    DATEADD(ms, -1 * (SELECT ms_ticks FROM sys.dm_os_sys_info) + timestamp, GETDATE()) AS event_time,
    CAST(record AS XML).value('(//Error/@value)[1]', 'int') AS error_num,
    CAST(record AS XML).value('(//Error/@value)[2]', 'int') AS error_num2,
    CAST(record AS XML).value('(//Error/@value)[3]', 'int') AS error_num3
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_EXCEPTION'
ORDER BY timestamp DESC;

-- OOM события
SELECT 
    DATEADD(ms, -1 * (SELECT ms_ticks FROM sys.dm_os_sys_info) + timestamp, GETDATE()) AS event_time,
    CAST(record AS XML).value('(//Record/@type)[1]', 'varchar(100)') AS event_type,
    CAST(record AS XML).value('(//Record/@id)[1]', 'int') AS event_id,
    CAST(record AS XML).value('(//Record/@value)[1]', 'bigint') AS event_value,
    CAST(record AS XML).value('(//Record/@data)[1]', 'varchar(max)') AS event_data
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_RESOURCE_MONITOR'
ORDER BY timestamp DESC;

1.10. Query Store — где включён

SELECT 
    name,
    is_query_store_on,
    desired_state_desc,
    actual_state_desc,
    current_storage_size_mb,
    max_storage_size_mb
FROM sys.database_query_store_options dqso
JOIN sys.databases d ON dqso.database_id = d.database_id
WHERE d.database_id > 4;

1.11. Extended Events — сессия для ловли тяжёлых запросов

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

-- Удалить старую сессию, если есть
IF EXISTS (SELECT 1 FROM sys.server_event_sessions WHERE name = 'HighCPU')
    DROP EVENT SESSION [HighCPU] ON SERVER;

-- Создать сессию
CREATE EVENT SESSION [HighCPU] ON SERVER
ADD EVENT sqlserver.sql_statement_completed (
    ACTION (sqlserver.sql_text, sqlserver.database_name, sqlserver.username, sqlserver.client_app_name)
    WHERE cpu_time > 5000
),
ADD EVENT sqlserver.rpc_completed (
    ACTION (sqlserver.sql_text, sqlserver.database_name, sqlserver.username, sqlserver.client_app_name)
    WHERE cpu_time > 5000
)
ADD TARGET package0.ring_buffer (SET max_memory = 4096)
WITH (MAX_DISPATCH_LATENCY = 5 SECONDS, STARTUP_STATE = OFF);
GO

-- Запустить
ALTER EVENT SESSION [HighCPU] ON SERVER STATE = START;
GO

-- Прочитать данные
SELECT 
    DATEADD(ms, -1 * (SELECT ms_ticks FROM sys.dm_os_sys_info), GETDATE()) AS session_start,
    xed.event_data.value('(event/@name)[1]', 'varchar(50)') AS event_name,
    xed.event_data.value('(event/@timestamp)[1]', 'varchar(50)') AS event_time,
    xed.event_data.value('(event/action[@name="sql_text"]/value)[1]', 'varchar(max)') AS sql_text,
    xed.event_data.value('(event/action[@name="database_name"]/value)[1]', 'varchar(128)') AS db_name,
    xed.event_data.value('(event/action[@name="username"]/value)[1]', 'varchar(128)') AS username,
    xed.event_data.value('(event/data[@name="cpu_time"]/value)[1]', 'bigint') AS cpu_time,
    xed.event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') AS duration
FROM (
    SELECT CAST(target_data AS XML) AS target_data
    FROM sys.dm_xe_session_targets st
    JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE s.name = 'HighCPU' AND st.target_name = 'ring_buffer'
) AS data
CROSS APPLY target_data.nodes('//RingBufferTarget/event') AS xed(event_data)
ORDER BY event_time DESC;

Остановить сессию после сбора: ALTER EVENT SESSION [HighCPU] ON SERVER STATE = STOP;


Шаг 2. Действия по результатам

Если топ-ожидание SOS_SCHEDULER_YIELD или CXPACKET

  1. Проверьте max degree of parallelism (рекомендуется 4–8 для OLTP, 1 для маленьких серверов).
  2. Проверьте cost threshold for parallelism (по умолчанию 5 — слишком мало, рекомендуется 25–50).
  3. Найдите топ-запросы (Шаг 1.4) и оптимизируйте их (индексы, план).
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'max degree of parallelism', 4; RECONFIGURE;
EXEC sp_configure 'cost threshold for parallelism', 50; RECONFIGURE;

Если PAGEIOLATCH_* и давление на память

  1. Ограничьте max server memory (оставьте 4 ГБ ОС).
  2. Проверьте недостающие индексы:
SELECT TOP 20
    migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) AS score,
    mid.statement AS table_name,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns,
    migs.user_seeks,
    migs.user_scans
FROM sys.dm_db_missing_index_group_stats migs
JOIN sys.dm_db_missing_index_groups mig ON migs.group_handle = mig.index_group_handle
JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
ORDER BY score DESC;

Если PREEMPTIVE_OS_QUERYREGISTRY (баг SQL Server 2022 RTM)

  1. Установить последнее CU (≥ CU5).
  2. Включить флаг трассировки 12502:
DBCC TRACEON (12502, -1);
DBCC TRACESTATUS(-1);

Если RESOURCE_SEMAPHORE_QUERY_COMPILE

  1. Включить optimize for ad hoc workloads:
EXEC sp_configure 'optimize for ad hoc workloads', 1; RECONFIGURE;

2. Проверить параметризацию — возможно, нужен forced parameterization для конкретной БД.

Если всплески CPU совпадают с IIS

  1. Проверить пулы приложений, включить 32-битный режим.
  2. Настроить recycling в непиковое время.
  3. Проверить, не является ли w3wp.exe причиной — возможно, утечка в приложении.

Если не хватает физической памяти

  1. Увеличить RAM (радикально).
  2. Ограничить потребление других процессов (антивирус, бэкапы).
  3. Настроить max server memory (оставить ОС 4 ГБ).

Шаг 3. Формирование пакета для анализа

После сбора данных у вас должна получиться папка примерно такого вида:

C:\Diag_20260921_132639\
├── cpu_total.blg
├── top_processes.txt
├── memory.txt
├── sql_services.txt
├── sql_version.txt
├── 01_cpu_ringbuffer.txt
├── 02_wait_stats.txt
├── 03_active_requests.txt
├── 04_top_cpu_queries.txt
├── 05_memory_status.txt
├── 06_config.txt
├── 07_running_jobs.txt
├── 08_blocking.txt
├── 09_ringbuffer_errors.txt
├── 10_query_store.txt
└── 11_xe_session.txt

Что прикладывать при передаче на анализ:

  • Все .txt файлы (можно заархивировать).
  • Скриншот Диспетчера задач / Монитора ресурсов.
  • Версию SQL Server (SELECT @@VERSION;).
  • Краткое описание проблемы: «тормозит постоянно / периодически / в определённое время».

Шаг 4. Чек-лист типовых проблем

Симптом Вероятная причина Что проверить Действие
Постоянно 45% CPU Баг SQL Server 2022 RTM Ожидание PREEMPTIVE_OS_QUERYREGISTRY Установить CU5+ и включить TF 12502
Постоянно высокий CPU Неоптимальные запросы Шаг 1.4 — топ по CPU Индексы, переписать запросы
Всплески по расписанию Задания SQL Agent Шаг 1.7 — активные задания Перенести в непиковое время
Всплески с IIS Много процессов w3wp.exe top_processes.txt Recycling, 32-битный режим
Высокий CPU + память 93% Давление на память Шаг 1.5 — память SQL max server memory, добавить RAM
Высокий CPU + PAGEIOLATCH Не хватает кэша Wait stats RAM или индексы
Высокий CPU + CXPACKET Параллелизм Шаг 1.6 — настройки MAXDOP, CTFP
Высокий CPU + LCK Блокировки Шаг 1.8 — блокировки Найти блокирующую сессию

Быстрые команды «одной строкой»

PowerShell

# Остановить/запустить SQL Server
Stop-Service MSSQL$SQLEXPRESS
Start-Service MSSQL$SQLEXPRESS
Restart-Service MSSQL$SQLEXPRESS

T-SQL

-- Сбросить wait stats (только осознанно!)
DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR);

-- Сбросить кэш планов (только осознанно!)
DBCC FREEPROCCACHE;

-- Сбросить кэш буфера (только осознанно!)
DBCC DROPCLEANBUFFERS;

-- Информация о версии
SELECT @@VERSION;
SELECT SERVERPROPERTY('ProductVersion'), SERVERPROPERTY('ProductLevel'), SERVERPROPERTY('Edition');

Резюме порядка действий

  1. Собрать выгрузки — Шаг 0 (PowerShell) + Шаг 1 (SQL).
  2. Посмотреть триаж-сводку — кто ест CPU, есть ли память.
  3. Найти виновника через wait stats и топ-запросы.
  4. Применить типовые меры из Шага 2.
  5. Если не помогает — скинуть пакет на анализ с описанием проблемы.

Сохраните этот документ как SQL_CPU_Runbook и держите под рукой. При каждом инциденте — идёте по шагам и собираете выгрузки.

Версия документа: 1.0 · Область применения: Microsoft SQL Server 2016–2022 (включая Express) · ОС: Windows Server 2016+

Насколько полезной была статья?

Что еще посмотреть по SQL Server

SQL Management Studio медленно работает, тормозит. Как решить проблему

Ошибки в SQL запросах и хранимых процедурах

Не запускается Configuration Manager

Решение проблем MS SQL Server с блокировками

Решение ошибки Cannot resolve the collation conflict between

SQL. Ошибка. Transaction (Process ID) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

SQL. Получение null при конкатенации (объединении) строк

SQL. Проблема с доступом к таблице БД

SQL Server. Ошибка Table:String or binary data would be truncated. The statement has been terminated.

Сколько памяти использует SQL Server

Дедлоки при update, insert

Высокое значение Resource Monitor в sp_who2 (загрузка CPU больше 50%)

Дополнительный заработок для разработчиков на T-SQL

Прямая работа с заказчиками как ИП или самозанятый. Нужно знать только SQL и HTML.
Falcon Space - платформа для создания сайтов с личными кабинетами
В 2-3 раза экономнее и быстрее, чем заказная разработка
Более гибкая, чем коробочные решения и облачные сервисы
Используйте готовые решения и изменяйте под свои потребности
Запрос расчета стоимости веб-проекта на базе Falcon Space
Если видео Youtube плохо грузится, то попробуйте найти видео в ВК видео на канале Falcon Space
Сайт использует Cookie, Яндекс Метрику. Используя сайт, вы соглашаетесь с правилами сайта. См. Правила конфиденциальности и Правила использования сайта OK