先确认sql server是否真在吃cpu:若任务管理器中sqlservr.exe的% privileged time远高于% user time,则可能是防病毒软件等系统级干扰;用powershell采样% user time连续5次>70才确认是sql server主动消耗cpu。

先确认是不是SQL Server真在吃CPU
别急着查存储过程,先看操作系统层有没有“假高”。打开任务管理器 → “详细信息”页,找 sqlservr.exe 进程的 CPU 占用率。如果它稳定在 80%+,再往下走;但如果 % Privileged Time(内核态)远高于 % User Time(用户态),比如 Privileged 占 92%、User 只有 15%,那大概率是防病毒软件、驱动或 Windows 更新在后台捣乱,不是 SQL Server 的锅。
更准一点,用 PowerShell 快速验证:
Get-Counter "\Process(sqlservr*)\% User Time" -MaxSamples 5 | ForEach-Object { $_.CounterSamples.CookedValue }
连续 5 次采样都 >70,才说明 SQL Server 确实在主动消耗 CPU 周期。
直接揪出正在燃烧CPU的存储过程会话
重点不是“哪个存储过程被调用”,而是“哪个会话正在执行它且卡在 CPU 上”。sys.dm_exec_requests 是第一现场,必须加过滤条件避免噪音:
- 只查
status IN ('running', 'suspended')—— 已结束或休眠的会话不参与当前 CPU 竞争 - 限定
cpu_time > 10000(毫秒),排除毛刺型短查询 - 加
command IN ('EXECUTE', 'RPC')—— 这类 command 值大概率指向存储过程入口,比查text里含EXEC更可靠 - 排除系统会话:
session_id > 50
完整语句示例:
SELECT r.session_id, s.login_name, s.host_name, t.text AS actual_sql, r.cpu_time, r.logical_reads, r.wait_type
FROM sys.dm_exec_requests r
JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.status IN ('running','suspended')
AND r.command IN ('EXECUTE', 'RPC')
AND r.cpu_time > 10000
AND s.session_id > 50;
定位存储过程内部的CPU热点代码
拿到 actual_sql 后,别只盯着 EXEC 那一行。真正烧 CPU 的,往往藏在存储过程体里。常见陷阱有:
-
SELECT中调用标量函数(UDF):每行执行一次,无法并行,sys.dm_exec_query_stats里能看到execution_count高但total_worker_time更高 - 用
WHILE循环处理数据:执行计划里会出现大量Compute Scalar或Table Spool,CPU 时间集中在循环体内 - 临时表没建索引就做
JOIN或ORDER BY:触发隐式排序,内存授予暴涨,CPU 被计划生成和排序操作吃掉 - 参数未参数化,用字符串拼接进
EXEC:每次执行都重编译,plan_generation_num > 1,编译本身就很耗 CPU
查法:对目标存储过程名,运行:
SELECT * FROM sys.dm_exec_procedure_stats
WHERE object_id = OBJECT_ID('YourProcName');
重点关注 total_worker_time 和 cached_time,再用 sys.dm_exec_query_plan(plan_handle) 提取 XML 执行计划,搜索 <relop logicalop="Compute Scalar"></relop> 或 <relop logicalop="Table Spool"></relop>。
别忽略统计信息和执行计划退化
同一个存储过程,今天 CPU 高、昨天正常?八成是统计信息过时或参数嗅探(Parameter Sniffing)翻车了。尤其当存储过程带参数、且数据分布倾斜时(比如查“张三”快、查“王五”慢),优化器可能缓存了一个对某参数高效的计划,但对其他参数就是灾难。
快速验证方式:
- 查该过程最近几次执行的等待类型:
last_wait_type是CXPACKET或SOS_SCHEDULER_YIELD,说明 CPU 在忙计算而非等 I/O - 对比
sys.dm_exec_procedure_stats中的min_elapsed_time和max_elapsed_time,差值超 10 倍基本可判定计划退化 - 手动更新统计信息:
UPDATE STATISTICS YourTable WITH FULLSCAN;,再清空该过程缓存:DBCC FREEPROCCACHE (@plan_handle);
临时缓解可用 WITH RECOMPILE 或查询提示 OPTION (RECOMPILE),但长期方案得靠参数化 + 正确的统计信息维护策略。
最常被跳过的一步:查完存储过程,顺手看下 sys.dm_os_schedulers 里的 current_tasks_count 和 runnable_tasks_count。如果 runnable 长期 > 1,说明 CPU 核心已经排队,这时优化单个存储过程效果有限,得优先处理锁争用或并发控制逻辑。











