如何通过数据库获取特定实例的 CPU 使用率?

got*_*tqn 15 performance sql-server sql-server-2012

我发现以下查询可以按数据库检测 CPU 使用率,但它们显示不同的结果:

WITH DB_CPU_Stats
AS
(
    SELECT DatabaseID, DB_Name(DatabaseID) AS [DatabaseName], 
      SUM(total_worker_time) AS [CPU_Time_Ms]
    FROM sys.dm_exec_query_stats AS qs
    CROSS APPLY (
                    SELECT CONVERT(int, value) AS [DatabaseID] 
                  FROM sys.dm_exec_plan_attributes(qs.plan_handle)
                  WHERE attribute = N'dbid') AS F_DB
    GROUP BY DatabaseID
)
SELECT ROW_NUMBER() OVER(ORDER BY [CPU_Time_Ms] DESC) AS [row_num],
       DatabaseName,
        [CPU_Time_Ms], 
       CAST([CPU_Time_Ms] * 1.0 / SUM([CPU_Time_Ms]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [CPUPercent]
FROM DB_CPU_Stats
--WHERE DatabaseID > 4 -- system databases
--AND DatabaseID <> 32767 -- ResourceDB
ORDER BY row_num OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

上面的查询表明问题出在我的一个数据库上(几乎 96%)。

下面的查询表明问题出在主数据库和分发数据库上(大约 90%):

DECLARE @total INT
SELECT @total=sum(cpu) FROM sys.sysprocesses sp (NOLOCK)
    join sys.sysdatabases sb (NOLOCK) ON sp.dbid = sb.dbid

SELECT sb.name 'database', @total 'system cpu', SUM(cpu) 'database cpu', CONVERT(DECIMAL(4,1), CONVERT(DECIMAL(17,2),SUM(cpu)) / CONVERT(DECIMAL(17,2),@total)*100) '%'
FROM sys.sysprocesses sp (NOLOCK)
JOIN sys.sysdatabases sb (NOLOCK) ON sp.dbid = sb.dbid
--WHERE sp.status = 'runnable'
GROUP BY sb.name
ORDER BY CONVERT(DECIMAL(4,1), CONVERT(DECIMAL(17,2),SUM(cpu)) / CONVERT(DECIMAL(17,2),@total)*100) desc
Run Code Online (Sandbox Code Playgroud)

我已经检查过sys.sysprocesses是否已弃用。这是否意味着第二个查询的结果是错误的?

Sol*_*zky 14

虽然我和@Thomas 一样,完全同意 @Aaron 在关于“每个数据库 CPU 使用率”的问题的评论中是准确还是有用的问题,但我至少可以回答关于为什么这两个查询如此如此的问题不同的。并且它们不同的原因将表明哪个更准确,尽管更高的准确度仍然相对于特别不准确的那个,因此仍然不是真正准确;-)。

第一个查询使用sys.dm_exec_query_stats来获取 CPU 信息(即total_worker_time)。如果您转到该 DMV 的 MSDN 文档链接页面,您将看到一个简短的 3 句介绍,其中 2 个句子为我们提供了了解此信息上下文所需的大部分内容(“它有多可靠”和“它与“相比如何sys.sysprocesses”)。这两句话是:

返回 SQL Server 中缓存查询计划的聚合性能统计信息。... 当一个计划从缓存中移除时,相应的行也会从此视图中删除

第一句,“返回聚合性能统计”,告诉我们这个 DMV 中的信息(就像其他几个一样)是累积的,而不是特定于当前正在运行的查询。这也由该 DMV 中的一个字段指示,该字段不是问题中查询的一部分execution_count,这再次表明这是累积数据。将这些数据累积起来非常方便,因为您可以通过将某些指标除以execution_count.

第二句话,“从缓存中删除的计划也从这个 DMV 中删除”,表明它根本不是一个完整的图片,特别是如果服务器已经有一个非常完整的计划缓存并且负载不足,因此计划到期有点频繁。此外,大多数 DMV 会在服务器重置时重置,因此即使这些行在计划到期时未删除,它们也不是真实的历史记录。

现在让我们将上面的与sys.sysprocesses. 此系统视图仅显示当前正在运行的内容,就像sys.dm_exec_connectionssys.dm_exec_sessionssys.dm_exec_requests的组合(在 的链接页面上说明sys.dm_exec_sessions)。与sys.dm_exec_query_stats即使在过程完成后仍保存数据的DMV相比,这是一个完全不同的服务器视图。意思是,关于“第二个查询的结果是否错误?” 问题,它们没有错,它们只是与性能统计的不同方面(即时间范围)有关。

因此,使用的查询sys.sysprocesses仅查看“现在”。并且使用的查询主要(可能)sys.dm_exec_query_stats查看自上次重新启动 SQL Server 服务(或显然是系统重新启动)以来发生的事情。对于一般性能分析,这似乎要好得多,但同样,它总是会丢失有用的信息。而且,在这两种情况下,您还需要首先考虑@Aaron 在问题评论(自删除后)中提出的关于“database_id”值准确性的观点(即它仅反映启动代码的活动数据库) ,不一定是“问题”发生的地方)。sys.dm_exec_query_stats

但是,如果你只需要/想什么是所有数据库中现在发生的事情的感觉,可能是因为事情正在放缓,现在,你最好使用的组合sys.dm_exec_connectionssys.dm_exec_sessionssys.dm_exec_requests(而不是过时的sys.sysprocesses)。请记住,您正在查看/查找查询,而不是数据库,因为查询可以跨多个数据库连接,包括来自一个或多个数据库的 UDF 等。


编辑:
如果总体关注的是减少高 CPU 消耗量,那么查找占用 CPU 最多的查询,因为数据库实际上并不占用 CPU(查看每个数据库可能在托管公司工作,其中每个数据库都是隔离的,并且由不同的客户拥有)。

以下查询将有助于识别平均 CPU 使用率较高的查询。它压缩了 query_stats DMV 中的数据,因为这些记录可以多次显示相同的查询(是的,查询批处理的相同子集),每次都有不同的执行计划。

;WITH cte AS
(
  SELECT stat.[sql_handle],
         stat.statement_start_offset,
         stat.statement_end_offset,
         COUNT(*) AS [NumExecutionPlans],
         SUM(stat.execution_count) AS [TotalExecutions],
         ((SUM(stat.total_logical_reads) * 1.0) / SUM(stat.execution_count)) AS [AvgLogicalReads],
         ((SUM(stat.total_worker_time) * 1.0) / SUM(stat.execution_count)) AS [AvgCPU]
  FROM sys.dm_exec_query_stats stat
  GROUP BY stat.[sql_handle], stat.statement_start_offset, stat.statement_end_offset
)
SELECT CONVERT(DECIMAL(15, 5), cte.AvgCPU) AS [AvgCPU],
       CONVERT(DECIMAL(15, 5), cte.AvgLogicalReads) AS [AvgLogicalReads],
       cte.NumExecutionPlans,
       cte.TotalExecutions,
       DB_NAME(txt.[dbid]) AS [DatabaseName],
       OBJECT_NAME(txt.objectid, txt.[dbid]) AS [ObjectName],
       SUBSTRING(txt.[text], (cte.statement_start_offset / 2) + 1,
       (
         (CASE cte.statement_end_offset 
           WHEN -1 THEN DATALENGTH(txt.[text])
           ELSE cte.statement_end_offset
          END - cte.statement_start_offset) / 2
         ) + 1
       )
FROM cte
CROSS APPLY sys.dm_exec_sql_text(cte.[sql_handle]) txt
ORDER BY cte.AvgCPU DESC;
Run Code Online (Sandbox Code Playgroud)