目录
在Azure SQL数据库/托管实例中,我们通常会使用Azure Portal上的Performance相关选项卡/SSMS工具中的Query Store图形化功能/SSMS中直接执行T-SQL来判断是哪些SQL导致PAAS服务CPU占有率很高。本例是基于我们公司最近双11提前预售活动时遇到的一次性能问题改编,我将使用实际生产库作为环境运行语句,供大家参考和探讨。
诊断高 CPU 事件时,了解可供数据库使用的虚拟核心 (vCore) 数会十分有用。 vCore 等效于逻辑 CPU。 vCore 数有助于了解数据库可用的 CPU 资源。使用 Transact-SQL 确定 vCore 计数的语句如下:
- SELECT
- COUNT(*) as vCores
- FROM sys.dm_os_schedulers
- WHERE status = N'VISIBLE ONLINE';

备注
对于使用 Gen4 硬件的数据库,
sys.dm_os_schedulers中的可见联机计划程序数可能是在创建数据库时指定的 vCore 数的两倍,会显示在 Azure 门户中。
首先用一段T-SQL来判断下是否数据库里存在大量死锁,如果存在大量死锁后可以从得出的结果里大致判断是什么语句以及什么表或视图牵扯到死锁。
- SELECT spid,
- blocked,
- DB_NAME(sp.dbid) AS DBName,
- program_name,
- waitresource,
- lastwaittype,
- sp.loginame,
- sp.hostname,
- a.[Text] AS [TextData],
- SUBSTRING(A.text, sp.stmt_start / 2,
- (CASE WHEN sp.stmt_end = -1 THEN DATALENGTH(A.text) ELSE sp.stmt_end
- END - sp.stmt_start) / 2) AS [current_cmd]
- FROM sys.sysprocesses AS sp OUTER APPLY sys.dm_exec_sql_text (sp.sql_handle) AS A
- WHERE spid > 50
- and sp.blocked <> 0 --这里blocked字段表示是否处于死锁阻塞,如果值不为0则说明死锁,反之则不死锁
- ORDER BY blocked DESC, DB_NAME(sp.dbid) ASC, a.[text];

如果在这段T-SQL中我们将WHERE中的条件sp.blocked<>0改为=0的话,则结果如下,我们可以看到很多处于非死锁状态下的进程的明细(比如来自什么数据库,数据库连接登录名是什么,来访主机资源名称是什么以及实际执行的SQL语句又是什么等重要信息)

如若找到死锁的记录条,则使用kill命令去杀掉blocked字段里的ID值即可,格式如下:
KILL ID(blocked)
假设blocked里的值为8488:
kill 8488;
在上一步骤中假设没有死锁的会话或语句,则我们重点考虑如何拉出执行效率比较低的语句。
通过执行以下查询,使用 CPU 使用率和执行计划查找当前正在运行的查询。 CPU 时间以毫秒为单位返回。
- SELECT
- req.session_id,
- req.status,
- req.start_time,
- req.cpu_time AS 'cpu_time_ms',
- req.logical_reads,
- req.dop,
- s.login_name,
- s.host_name,
- s.program_name,
- object_name(st.objectid,st.dbid) 'ObjectName',
- REPLACE (REPLACE (SUBSTRING (st.text,(req.statement_start_offset/2) + 1,
- ((CASE req.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
- ELSE req.statement_end_offset END - req.statement_start_offset)/2) + 1),
- CHAR(10), ' '), CHAR(13), ' ') AS statement_text,
- qp.query_plan,
- qsx.query_plan as query_plan_with_in_flight_statistics
- FROM sys.dm_exec_requests as req
- JOIN sys.dm_exec_sessions as s on req.session_id=s.session_id
- CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) as st
- OUTER APPLY sys.dm_exec_query_plan(req.plan_handle) as qp
- OUTER APPLY sys.dm_exec_query_statistics_xml(req.session_id) as qsx
- ORDER BY req.cpu_time desc;

此查询返回执行计划的两个副本。 列 query_plan 包含来自 sys.dm_exec_query_plan()
的执行计划。 此版本的查询计划仅包含行计数的估计值,不包含任何执行统计信息。
如果列 query_plan_with_in_flight_statistics 返回执行计划,则此计划会提供详细信息。 query_plan_with_in_flight_statistics 列从
sys.dm_exec_query_statistics_xml() 返回数据,其中包括“正在进行”的执行统计信息,例如当前正在运行的查询到目前为止返回的实际行数。
针对 sys.dm_db_resource_stats 的以下查询会返回过去大约一小时的平均 CPU 使用率(按 15 秒间隔)。
- SELECT
- end_time,
- avg_cpu_percent,
- avg_instance_cpu_percent
- FROM sys.dm_db_resource_stats
- ORDER BY end_time DESC;

不仅关注 avg_cpu_percent 列十分重要。 avg_instance_cpu_percent 列包括用户和内部工作负载使用的 CPU。 如果 avg_instance_cpu_percent 接近 100%,CPU 资源会饱和。 在这种情况下,如果应用吞吐量不足或查询延迟较高,应对高 CPU 使用率问题进行故障排除。
查询存储会跟踪查询的执行统计信息,包括 CPU 使用率。 下面的查询返回过去 2 小时内运行的排名靠前的 15 个查询(按 CPU 使用率排序)。 CPU 时间以毫秒为单位返回。
前提:在SSMS中要切换到业务所在的数据库!!!!!
- WITH AggregatedCPU AS
- (SELECT
- q.query_hash,
- SUM(count_executions * avg_cpu_time / 1000.0) AS total_cpu_ms,
- SUM(count_executions * avg_cpu_time / 1000.0)/ SUM(count_executions) AS avg_cpu_ms,
- MAX(rs.max_cpu_time / 1000.00) AS max_cpu_ms,
- MAX(max_logical_io_reads) max_logical_reads,
- COUNT(DISTINCT p.plan_id) AS number_of_distinct_plans,
- COUNT(DISTINCT p.query_id) AS number_of_distinct_query_ids,
- SUM(CASE WHEN rs.execution_type_desc='Aborted' THEN count_executions ELSE 0 END) AS aborted_execution_count,
- SUM(CASE WHEN rs.execution_type_desc='Regular' THEN count_executions ELSE 0 END) AS regular_execution_count,
- SUM(CASE WHEN rs.execution_type_desc='Exception' THEN count_executions ELSE 0 END) AS exception_execution_count,
- SUM(count_executions) AS total_executions,
- MIN(qt.query_sql_text) AS sampled_query_text
- FROM sys.query_store_query_text AS qt
- JOIN sys.query_store_query AS q ON qt.query_text_id=q.query_text_id
- JOIN sys.query_store_plan AS p ON q.query_id=p.query_id
- JOIN sys.query_store_runtime_stats AS rs ON rs.plan_id=p.plan_id
- JOIN sys.query_store_runtime_stats_interval AS rsi ON rsi.runtime_stats_interval_id=rs.runtime_stats_interval_id
- WHERE
- rs.execution_type_desc IN ('Regular', 'Aborted', 'Exception') AND
- rsi.start_time>=DATEADD(HOUR, -2, GETUTCDATE())
- GROUP BY q.query_hash),
- OrderedCPU AS
- (SELECT *,
- ROW_NUMBER() OVER (ORDER BY total_cpu_ms DESC, query_hash ASC) AS RN
- FROM AggregatedCPU)
- SELECT *
- FROM OrderedCPU AS OD
- WHERE OD.RN<=15
- ORDER BY total_cpu_ms DESC;

此查询为 query_hash 在整个查询存储历史记录中的每个执行计划变体返回一行。 结果会按总 CPU 时间进行排序。同时,sampled_query_text可以直接看出是具体哪句执行语句命令消耗资源较高。