SQLServer性能排查之CPU使用率过高的问题

news2024/11/19 15:24:49

【背景】

华北某SQLServer数据库从12月起出现CPU使用率过高的情况,一直未能够找到原因。当时电话简单将思路进行分享,反馈SQLServer中出现CPU使用率过高有许多可能原因,但常见的原因如下最为常见:

  • 由于以下情况,表或索引扫描导致的高逻辑读取:
    • 过期统计信息
    • 缺少索引
    • 设计不佳的查询
  • 工作负载增加

针对此类问题,微软有一套的排查套路,整理记录在此。

【套路】

步骤1,验证 SQL Server 是否导致 CPU 使用率过高

使用以下工具之一检查 SQL Server 进程是否确实导致 CPU 使用率过高:

  • 任务管理器:在“进程”选项卡上,检查“64 位版本的 SQL Server Windows NT”的“CPU”列的值是否接近 100%。
  • 性能和资源监视器
    • 计数器:Process/%User Time, % Privileged Time
    • 实例:sqlservr
  • 可以使用以下 PowerShell 脚本在 60 秒的跨度内收集计数器数据:
$serverName = $env:COMPUTERNAME
$Counters = @(
    ("\\$serverName" + "\Process(sqlservr*)\% User Time"), ("\\$serverName" + "\Process(sqlservr*)\% Privileged Time")
)
Get-Counter -Counter $Counters -MaxSamples 30 | ForEach {
    $_.CounterSamples | ForEach {
        [pscustomobject]@{
            TimeStamp = $_.TimeStamp
            Path = $_.Path
            Value = ([Math]::Round($_.CookedValue, 3))
        }
        Start-Sleep -s 2
    }
}

如果 % User Time 始终大于 90%,则是 SQL Server 进程导致 CPU 使用率过高。 但是,如果 % Privileged time 始终大于 90%,则是防病毒软件、其他驱动程序或计算机上的其他操作系统组件导致 CPU 使用率过高。

步骤2,确定影响 CPU 使用率的查询

如果 Sqlservr.exe 进程导致 CPU 使用率过高,则最常见的原因是执行表或索引扫描的 SQL Server 查询,其次是排序、哈希操作和循环 (嵌套循环运算符或 WHILE (T-SQL) ) 。 要了解查询当前在总 CPU 使用率中的占比,请运行以下语句:

DECLARE @init_sum_cpu_time int,
        @utilizedCpuCount int 
--get CPU count used by SQL Server
SELECT @utilizedCpuCount = COUNT( * )
FROM sys.dm_os_schedulers
WHERE status = 'VISIBLE ONLINE' 
--calculate the CPU usage by queries OVER a 5 sec interval 
SELECT @init_sum_cpu_time = SUM(cpu_time)
FROM sys.dm_exec_requests WAITFOR DELAY '00:00:05'SELECT CONVERT(DECIMAL(5,
         2),
         ((SUM(cpu_time) - @init_sum_cpu_time) / (@utilizedCpuCount * 5000.00)) * 100) AS [CPU FROM Queries AS Percent of Total CPU Capacity]
FROM sys.dm_exec_requests

若要确定当前负责高 CPU 活动的查询,请运行以下语句:

SELECT TOP 10 s.session_id,
           r.status,
           r.cpu_time,
           r.logical_reads,
           r.reads,
           r.writes,
           r.total_elapsed_time / (1000 * 60) 'Elaps M',
           SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,
           ((CASE r.statement_end_offset
                WHEN -1 THEN DATALENGTH(st.TEXT)
                ELSE r.statement_end_offset
            END - r.statement_start_offset) / 2) + 1) AS statement_text,
           COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid)) 
           + N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,
           r.command,
           s.login_name,
           s.host_name,
           s.program_name,
           s.last_request_end_time,
           s.login_time,
           r.open_transaction_count
FROM sys.dm_exec_sessions AS s
JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st
WHERE r.session_id != @@SPID
ORDER BY r.cpu_time DESC

如果查询目前未驱动 CPU,可以运行以下语句来查找历史占用大量 CPU 的查询:

SELECT TOP 10 st.text AS batch_text,
    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 statement_text,
    (qs.total_worker_time / 1000) / qs.execution_count AS avg_cpu_time_ms,
    (qs.total_elapsed_time / 1000) / qs.execution_count AS avg_elapsed_time_ms,
    qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
    (qs.total_worker_time / 1000) AS cumulative_cpu_time_all_executions_ms,
    (qs.total_elapsed_time / 1000) AS cumulative_elapsed_time_all_executions_ms
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(sql_handle) st
ORDER BY(qs.total_worker_time / qs.execution_count) DESC

步骤 3:更新统计信息

在确定 CPU 占用最高的查询后,请更新这些查询使用的表的“更新统计信息” 。 可以使用 sp_updatestats 系统存储过程更新当前数据库中所有用户定义表和内部表的统计信息。

exec sp_updatestats

sp_updatestats系统存储过程针对当前数据库中的所有用户定义表和内部表运行UPDATE STATISTICS。

步骤 4:添加缺失索引

缺少索引可能导致运行速度较慢的查询和 CPU 使用率过高。 可以识别缺失的索引并创建这些索引,以改善这种性能影响。

  1. 运行以下查询以识别导致 CPU 使用率高且在查询计划中至少包含一个缺失索引的查询:
-- Captures the Total CPU time spent by a query along with the query plan and total executions
SELECT
    qs_cpu.total_worker_time / 1000 AS total_cpu_time_ms,
    q.[text],
    p.query_plan,
    qs_cpu.execution_count,
    q.dbid,
    q.objectid,
    q.encrypted AS text_encrypted
FROM
    (SELECT TOP 500 qs.plan_handle,
     qs.total_worker_time,
     qs.execution_count FROM sys.dm_exec_query_stats qs ORDER BY qs.total_worker_time DESC) AS qs_cpu
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS q
CROSS APPLY sys.dm_exec_query_plan(plan_handle) p
WHERE p.query_plan.exist('declare namespace 
        qplan = "http://schemas.microsoft.com/sqlserver/2004/07/showplan";
        //qplan:MissingIndexes')=1
  1. 查看已标识查询的执行计划,并通过进行所需的更改来优化查询。 以下屏幕截图显示了一个示例,其中 SQL Server 将指出查询的缺失索引。 右键单击查询计划的“缺失索引”部分,然后选择“缺少索引详细信息”,在 SQL Server Management Studio 的另一个窗口中创建索引。

在这里插入图片描述
3. 使用以下查询检查是否缺少索引,并应用具有高改进度量值的任何建议索引。 从输出中具有最高 improvement_measure 值的前 5 或 10 条建议开始。 这些索引对性能有最显著的积极影响。 确定是否要应用这些索引,并确保对应用程序进行了性能测试。 然后,继续应用缺失索引建议,直到获得所需的应用程序性能结果。

SELECT CONVERT(VARCHAR(30), GETDATE(), 126) AS runtime,
    mig.index_group_handle,
    mid.index_handle,
    CONVERT(DECIMAL(28, 1), migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans)) AS improvement_measure,
    'CREATE INDEX missing_index_' + CONVERT(VARCHAR, mig.index_group_handle) + '_' + CONVERT(VARCHAR, mid.index_handle) + ' ON ' + mid.statement + ' (' + ISNULL(mid.equality_columns,
        '') + CASE WHEN mid.equality_columns IS NOT NULL
AND mid.inequality_columns IS NOT NULL THEN ','
ELSE ''
END + ISNULL(mid.inequality_columns,
        '') + ')' + ISNULL(' INCLUDE (' + mid.included_columns + ')',
        '') AS create_index_statement,
    migs.*,
    mid.database_id,
    mid.[object_id]
FROM sys.dm_db_missing_index_groups mig
INNER JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE CONVERT (DECIMAL (28, 1),
               migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans)) > 10
ORDER BY migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) DESC

步骤 5:调查并解决参数敏感问题

可以使用 DBCC FREEPROCCACHE 命令释放计划缓存,并检查这是否解决了 CPU 使用率过高的问题。 如果问题已修复,则表示是参数敏感问题(PSP,也称为“参数探查问题”)。

使用不带参数的 DBCC FREEPROCCACHE 将从计划缓存中删除所有已编译的计划。 这将导致再次编译新的查询执行,从而导致每个新查询的持续时间一次性延长。 最佳方法是使用 DBCC FREEPROCCACHE ( plan_handle | sql_handle ) 来识别导致问题的查询,然后解决单个查询或有问题的查询。

将 DBCC FREEPROCCACHE 命令用作临时解决方案,直到应用程序代码修复为止。 可以使用 DBCC FREEPROCCACHE (plan_handle) 命令仅删除导致问题的计划。 例如,若要查找引用 AdventureWorks 中 Person.Person 表的查询计划,可以使用此查询查找查询句柄。 然后,可以使用查询结果第二列中生成的 DBCC FREEPROCCACHE (plan_handle),从缓存中释放特定查询计划。

SELECT text, 'DBCC FREEPROCCACHE (0x' + CONVERT(VARCHAR (512), plan_handle, 2) + ')' AS dbcc_freeproc_command FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
WHERE text LIKE '%person.person%'

步骤 6:调查并解决 SARGability 问题

当 SQL Server 引擎可以使用索引查找来加快查询的执行速度时,查询中的谓词将被视为 SARGable (Search ARGument-able)。 许多查询设计会阻止 SARGability,并导致表或索引扫描和 CPU 使用率过高。 请考虑 AdventureWorks 数据库的以下查询,其中必须检索每个 ProductNumber 并向其应用 SUBSTRING() 函数,然后再将其与字符串文本值进行比较。 如你所见,必须先提取表的所有行,然后应用函数,然后才能进行比较。 从表中提取所有行意味着表或索引扫描,这会导致 CPU 使用率较高。

# 函数查询方法
SELECT ProductID, Name, ProductNumber
FROM [Production].[Product]
WHERE SUBSTRING(ProductNumber, 0, 4) =  'HN-'
# 谓词模糊方法
SELECT ProductID, Name, ProductNumber
FROM [Production].[Product]
WHERE Name LIKE  'Hex%'
# 谓词计算方法 
SELECT DISTINCT SalesOrderID, UnitPrice, UnitPrice * 0.10 [10% Commission]
FROM [Sales].[SalesOrderDetail]
WHERE UnitPrice * 0.10 > 300

步骤 7:禁用重度跟踪

检查 SQL 跟踪或 XEvent 跟踪,这些跟踪会影响 SQL Server 性能并导致 CPU 使用率过高。 例如,如果跟踪重度 SQL Server 活动,使用以下事件可能会导致 CPU 使用率过高:

  • 查询计划 XML 事件 (query_plan_profile, query_post_compilation_showplan, query_post_execution_plan_profile,
  • query_post_execution_showplan, query_pre_execution_showplan)
  • 语句级事件 (sql_statement_completed, sql_statement_starting, sp_statement_starting, sp_statement_completed)
  • 登录和注销事件 (login, process_login_finish, login_event, logout)
  • 锁定事件 (lock_acquired, lock_cancel, lock_released)
  • 等待事件 (wait_info, wait_info_external)
  • SQL 审核事件(取决于该组中审核的组和 SQL Server 活动)

运行以下查询以确定活动的 XEvent 或 Server 跟踪:

PRINT '--Profiler trace summary--'
SELECT traceid, property, CONVERT(VARCHAR(1024), value) AS value FROM::fn_trace_getinfo(
    default)
GO
PRINT '--Trace event details--'
SELECT trace_id,
    status,
    CASE WHEN row_number = 1 THEN path ELSE NULL end AS path,
    CASE WHEN row_number = 1 THEN max_size ELSE NULL end AS max_size,
    CASE WHEN row_number = 1 THEN start_time ELSE NULL end AS start_time,
    CASE WHEN row_number = 1 THEN stop_time ELSE NULL end AS stop_time,
    max_files,
    is_rowset,
    is_rollover,
    is_shutdown,
    is_default,
    buffer_count,
    buffer_size,
    last_event_time,
    event_count,
    trace_event_id,
    trace_event_name,
    trace_column_id,
    trace_column_name,
    expensive_event
FROM
    (SELECT t.id AS trace_id,
     row_number() over(PARTITION BY t.id order by te.trace_event_id, tc.trace_column_id) AS row_number,
     t.status,
     t.path,
     t.max_size,
     t.start_time,
     t.stop_time,
     t.max_files,
     t.is_rowset,
     t.is_rollover,
     t.is_shutdown,
     t.is_default,
     t.buffer_count,
     t.buffer_size,
     t.last_event_time,
     t.event_count,
     te.trace_event_id,
     te.name AS trace_event_name,
     tc.trace_column_id,
     tc.name AS trace_column_name,
     CASE WHEN te.trace_event_id in (23, 24, 40, 41, 44, 45, 51, 52, 54, 68, 96, 97, 98, 113, 114, 122, 146, 180) THEN CAST(1 as bit) ELSE CAST(0 AS BIT) END AS expensive_event FROM sys.traces t CROSS APPLY::fn_trace_geteventinfo(t.id) AS e JOIN sys.trace_events te ON te.trace_event_id = e.eventid JOIN sys.trace_columns tc ON e.columnid = trace_column_id) AS x
GO
PRINT '--XEvent Session Details--'
SELECT sess.NAME 'session_name', event_name, xe_event_name, trace_event_id,
    CASE WHEN xemap.trace_event_id IN(23, 24, 40, 41, 44, 45, 51, 52, 54, 68, 96, 97, 98, 113, 114, 122, 146, 180) 
    THEN Cast(1 AS BIT)
ELSE Cast(0 AS BIT)
END AS expensive_event
FROM sys.dm_xe_sessions sess
JOIN sys.dm_xe_session_events evt
ON sess.address = evt.event_session_address
INNER JOIN sys.trace_xe_event_map xemap
ON evt.event_name = xemap.xe_event_name
GO

步骤 8:配置虚拟机

如果使用的是虚拟机,请确保不会过度预配 CPU 并正确配置它们。
解决 ESX/ESXi 虚拟机性能问题 (2001003)

步骤 9:纵向扩展系统以使用更多 CPU

如果单个查询实例的 CPU 占用很低,但所有查询的总体工作负载共同导致 CPU 占用较高,请考虑通过添加更多 CPU 来纵向扩展计算机。 使用以下查询找出超过单个执行的平均 CPU 占用和最大 CPU 占用的特定阈值且已在系统上多次运行的查询数:

-- Shows queries where Max and average CPU time exceeds 200 ms and executed more than 1000 times
DECLARE @cputime_threshold_microsec INT = 200*1000
DECLARE @execution_count INT = 1000
SELECT qs.total_worker_time/1000 total_cpu_time_ms,
       qs.max_worker_time/1000 max_cpu_time_ms,
       (qs.total_worker_time/1000)/execution_count average_cpu_time_ms,
       qs.execution_count,
       q.[text]
FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS q
WHERE (qs.total_worker_time/execution_count > @cputime_threshold_microsec
        OR qs.max_worker_time > @cputime_threshold_microsec )
        AND execution_count > @execution_count
ORDER BY  qs.total_worker_time DESC

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/82138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】跳表Skiplist

文章目录跳表--skiplistskiplist的概念skilplist的原理skilplist的实现随机值函数跳表节点跳表框架查找函数寻找前置节点添加元素删除元素打印链表测试结果Skiplist与其他Key-Value结构的比较跳表–skiplist skiplist的概念 skiplist本质上也是一种查找结构,用于解…

产品待办列表梳理(PBR)是什么?

产品待办列表(PBL)是Scrum框架下最重要的一个工件(Artifact),产品待办列表的梳理(Product backlog Refinement-PBR)也是一个重要的活动,它不同于Scrum的3-3-5-5。仔细阅读Scrum指南,对产品待办列表梳理活动的描述是有限的: “只有…

R语言基于树的方法:决策树,随机森林,Bagging,增强树

概观 本文是有关 基于树的 回归和分类方法的。最近我们被客户要求撰写关于决策树的研究报告,包括一些图形和统计输出。 视频:从决策树到随机森林:R语言信用卡违约分析信贷数据实例 从决策树到随机森林:R语言信用卡违约分析信贷…

『微信小程序从0到1』视图与逻辑

🐱‍🐉🐱‍🐉🐱‍🐉 请乘理想之马,挥鞭从此起程,路上春色正好,天上太阳正晴🐾🐾🐾/font> 🌈博客主页👉白小…

【SQL】主从复制

主从复制主从复制的作用主从复制的原理一主一从架构主从配置文件1.主机配置2.从机配置3.主机建立账户并授权4.从机:配置需要复制的主机5.测试6.停止主从同步binlog_format三种格式双主双从架构如何提升数据库并发能力:在实际工作中,我们常常将…

B站运营,B站游戏数据如何分析?

B站聚集了大部分年轻人,用户量和黏性一直持续增长,在B站,游戏也是重点发展的一个分区,而对于运营者想做好游戏运营也并非易事,想做好运营掌握游戏数据必不可少,那么应该如何查看游戏数据呢? 一…

童年 高尔基

每周读一本经典,大概从2022年11月开始了,目前已经读了4本,csdn平台更适合技术分享,知乎比较适合书籍分享 《童年》是高尔基三部曲中的第一部。坐在柳东图书馆像海水浸过的沙子般座椅,周末的下午沉浸在高尔基先生奇特的…

线程池应用(四)

线程池应用线程池线程池应用多线程应用同步和异步1. 需要等待结果1. join 实现(同步)2. Future 实现(同步)3.CompletableFuture 实现(异步)4. BlockingQueue 实现(异步)2. 不需等待结…

5分钟搞定,实现 定时任务 的五种方案!

我们在实际开发中,多多少少都会用到定时任务来处理一些问题。 比如金融项目中的对账,每天定时对昨天的账务进行核对,每个月初对上个月的账务进行核对等。 还比如,我们需要处理一些老数据迁移,修复一些新项目和老项目…

基于java+springboot+mybatis+vue+mysql的财务管理系统

项目介绍 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时…

CI24R1/SI24R1 2.4G无线传输技术--无线门铃

无线门铃是一种基于2.4G无线传输技术的智能家居产品,主要用于亲朋好友探访的语音提醒功能。此次方案采用2.4G无线通信设计,室内和室外子母机组网,在智能家居及办公场所等方面得到广泛的应用。 一、应用场景 适用于居民社区、家庭、公寓、酒店…

2-2-3-5-5、Disruptor详解

目录简介juc包下的队列存在的问题设计方案RingBuffer数据结构数据存取方案常用等待策略写数据流程单线程(一个生产者)多线程(多个生产者)多个消费者读数据多个生产者写数据核心概念使用构造器引入依赖单生产者单消费者模式创建Eve…

微信公众号开发——接收用户消息(图文、语言、上报位置、关注、取消关注)及自动回复

😊 作者: 一恍过去💖 主页: https://blog.csdn.net/zhuocailing3390🎊 社区: Java技术栈交流🎉 主题: 微信公众号开发——接收用户消息(图文、语言、上报位置、关注、取消关注)及…

推动MRO工业品数字化基建升级,数商云采购系统赋能企业采购数字化管理

MRO工业品是工业生产中的重要组成部分,经历了十余年的发展成长,市场规模持续增长,然而据数据显示,MRO工业品市场的线上渗透率仍停留在个位数,这意味着MRO工业品数字化采购仍有巨大的发展空间。 MRO工业品行业发展受困…

非零基础自学Golang 第1章 走进Go 1.1 Go编程语言概述 1.1.1 Go 的历史

非零基础自学Golang 文章目录非零基础自学Golang第1章 走进Go1.1 Go编程语言概述1.1.1 Go 的历史第1章 走进Go 1.1 Go编程语言概述 Go语言也叫Golang,是由谷歌(Google)公司在2007年推出的一款静态编译型语言。Go语言高效、简洁、容易上手&a…

上海诺基亚贝尔-S-010W-AV2B卡刷固件包

上海诺基亚贝尔-S-010W-AV2B卡刷固件包 固件特点: 1、修改dns,三网通用; 2、开放原厂固件屏蔽的市场安装和u盘安装apk; 3、无开机广告,无系统更新,不在被强制升级; 4、大量精简内置的没用的…

最新版Crack:MailBee.NET 2022最后版

MailBee.NET Items Package 包括 SMTP、POP3、IMAP、EWS、Security、Antispam、Outlook Converter、Address Validator、PDF 部件,以及作为免费功能的 BounceMail、HTML、MIME、ICalVCard 部件。MailBee.NET Objects是一组功能强大且功能丰富的 .NET 元素&#xff0…

基于nodejs如何爬取csdn上自己的文章

当你想把自己在csdn上写的文章转为hexo上可以发布的文章或是将文章写入自己的数据库时,可以用到 将所有博客数据写入数据库 获取你的文章的分页接口: 在浏览自己的所有文章时,我们不难发现,文章的数据是往下滑动到底时,才会刷新出新的数据, 那么此时肯定是发送了一个请求来获…

一篇文章教你实战Docker容器数据卷

在上一篇中,咱们对Docker中的容器数据卷做了介绍。已经知道了容器数据卷是什么?能干什么用。那么本篇咱们就来实战容器数据卷,Docker容器数据卷案例主要做以下三个案例 1:宿主机(也就是Docker所安装的机器)与容器之间的映射-让Do…

LeetCode 538. 把二叉搜索树转换为累加树(C++)

标签:二叉树搜索 深度优先遍历 二叉树 思路一:递归实现反向中序遍历,并累加递归过程中的根的值 思路二:使用迭代,给每个根节点添加一个反向中序遍历的前驱节点。 原题链接:https://leetcode.cn/problems/co…