Como obter o uso da CPU pelo banco de dados para uma instância específica?

15

Encontrei as seguintes consultas para detectar o uso da CPU pelo banco de dados, mas eles estão mostrando resultados diferentes:

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);

A consulta acima informa que o problema está em um dos meus bancos de dados (quase 96%).

E a consulta abaixo informa que o problema ocorre com os bancos de dados mestre e de distribuição (cerca de 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

Eu verifiquei que o sys.sysprocessesfoi decretado. Isso significa que os resultados da segunda consulta estão incorretos?

gotqn
fonte

Respostas:

14

Embora eu, como o @Thomas, concorde completamente com o @Aaron nos comentários sobre a questão de que o "uso da CPU por banco de dados" seja preciso ou útil, posso pelo menos responder à pergunta de por que essas duas consultas são tão diferente. E a razão pela qual eles são diferentes indicará qual é mais preciso, embora esse nível mais alto de precisão ainda seja relativo àquele que é especificamente impreciso, portanto ainda não é verdadeiramente preciso ;-).

A primeira consulta usa sys.dm_exec_query_stats para obter informações da CPU (por exemplo total_worker_time). Se você for para a página vinculada que é a documentação do MSDN para essa DMV, você verá uma introdução curta de três frases e duas dessas frases nos fornecerão o que precisamos para entender o contexto dessas informações ("quão confiável é" e "como ele se compara a sys.sysprocesses"). Essas duas frases são:

Retorna estatísticas de desempenho agregadas para planos de consulta em cache no SQL Server. ... Quando um plano é removido do cache, as linhas correspondentes são eliminadas dessa visualização

A primeira frase, "Retorna estatísticas agregadas de desempenho", informa que as informações nesta DMV (assim como várias outras) são cumulativas e não são específicas apenas para consultas atualmente em execução. Isso também é indicado por um campo na DMV que não faz parte da consulta na Pergunta execution_count, que novamente mostra que esses são dados cumulativos. E é muito útil que esses dados sejam cumulativos, pois você pode obter médias etc. dividindo algumas das métricas peloexecution_count .

A segunda frase, "os planos que estão sendo removidos do cache também são removidos desta DMV", indica que não é uma imagem completa, especialmente se o servidor já tiver um cache de plano bastante completo e estiver sob carga e, portanto, expirar os planos. com alguma frequência. Além disso, a maioria das DMVs é redefinida quando o servidor é redefinido para que não seja um histórico verdadeiro, mesmo que essas linhas não tenham sido removidas quando os planos expirarem.

Agora vamos contrastar o acima com sys.sysprocesses. Essa visualização do sistema mostra apenas o que está em execução no momento, assim como a combinação de sys.dm_exec_connections , sys.dm_exec_sessions e sys.dm_exec_requests (indicado na página vinculada de sys.dm_exec_sessions). Essa é uma visão totalmente diferente do servidor em comparação com osys.dm_exec_query_stats DMV, que mantém os dados mesmo após a conclusão do processo. Ou seja, em relação ao "os resultados da segunda consulta estão errados?" pergunta, eles não estão errados, apenas dizem respeito a um aspecto diferente (ou seja, período de tempo) das estatísticas de desempenho.

Portanto, a consulta usando sys.sysprocessesestá apenas olhando "agora". E a consulta usando sys.dm_exec_query_statsestá olhando principalmente (talvez) o que aconteceu desde a última reinicialização do serviço SQL Server (ou obviamente a reinicialização do sistema). Para a análise geral de desempenho, parece que sys.dm_exec_query_statsé muito melhor, mas, novamente, descarta informações úteis o tempo todo. E, nos dois casos, você também precisa considerar os pontos apresentados pelo @Aaron nos comentários da pergunta (desde que removidos) em relação à precisão do valor "database_id" em primeiro lugar (ou seja, ele reflete apenas o banco de dados ativo que iniciou o código , não necessariamente onde o "problema" está ocorrendo).

Mas, se você só precisa / quer ter uma noção do que está acontecendo agora em todos os bancos de dados, possivelmente porque as coisas estão a abrandar agora, você é melhor fora de usar a combinação de sys.dm_exec_connections, sys.dm_exec_sessionse sys.dm_exec_requests(e não a obsoleta sys.sysprocesses). Lembre-se de que você está procurando / para consultas , não bancos de dados , porque as consultas podem se unir em vários bancos de dados, incluir UDFs de um ou mais bancos de dados, etc.


EDIT:
Se a preocupação geral é reduzir os consumidores de CPU alta, procure as consultas que ocupam mais CPU, porque os bancos de dados na verdade não ocupam CPU (a procura por banco de dados pode funcionar em uma empresa de hospedagem onde cada banco de dados está isolado e de propriedade de um cliente diferente).

A consulta a seguir ajudará a identificar consultas com alto uso médio da CPU. Condensa os dados no DMV query_stats, pois esses registros podem mostrar a mesma consulta (sim, o mesmo subconjunto do lote de consultas) várias vezes, cada um com um plano de execução diferente.

;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;
Solomon Rutzky
fonte
está AvgCPUem milissegundos?
Kolob Canyon
Oi @KolobCanyon. De acordo com a documentação para sys.dm_exec_query_stats , total_worker_timeé " A quantidade total de tempo de CPU, relatada em microssegundos (mas precisa apenas de milissegundos), consumida pelas execuções deste plano desde que ele foi compilado ". Isso ajuda? Isso pode ser facilmente convertido em milissegundos, se é assim que você deseja ver.
Solomon Rutzky 15/03/19
1

Ajustei a consulta para divisão por 0 erros e otimizei os nomes das colunas para copiar / colar no Excel.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
WITH DB_CPU_Stats
AS
(
    SELECT DatabaseID, isnull(DB_Name(DatabaseID),case DatabaseID when 32767 then 'Internal ResourceDB' else CONVERT(varchar(255),DatabaseID)end) AS [DatabaseName], 
      SUM(total_worker_time) AS [CPU_Time_Ms],
      SUM(total_logical_reads)  AS [Logical_Reads],
      SUM(total_logical_writes)  AS [Logical_Writes],
      SUM(total_logical_reads+total_logical_writes)  AS [Logical_IO],
      SUM(total_physical_reads)  AS [Physical_Reads],
      SUM(total_elapsed_time)  AS [Duration_MicroSec],
      SUM(total_clr_time)  AS [CLR_Time_MicroSec],
      SUM(total_rows)  AS [Rows_Returned],
      SUM(execution_count)  AS [Execution_Count],
      count(*) 'Plan_Count'
    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 [Rank_CPU],
       DatabaseName,
       [CPU_Time_Hr] = convert(decimal(15,2),([CPU_Time_Ms]/1000.0)/3600) ,
        CAST([CPU_Time_Ms] * 1.0 / SUM(case [CPU_Time_Ms] when 0 then 1 else [CPU_Time_Ms] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [CPU_Percent],
       [Duration_Hr] = convert(decimal(15,2),([Duration_MicroSec]/1000000.0)/3600) , 
       CAST([Duration_MicroSec] * 1.0 / SUM(case [Duration_MicroSec] when 0 then 1 else [Duration_MicroSec] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Duration_Percent],    
       [Logical_Reads],
        CAST([Logical_Reads] * 1.0 / SUM(case [Logical_Reads] when 0 then 1 else [Logical_Reads] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Logical_Reads_Percent],      
       [Rows_Returned],
        CAST([Rows_Returned] * 1.0 / SUM(case [Rows_Returned] when 0 then 1 else [Rows_Returned] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Rows_Returned_Percent],
       [Reads_Per_Row_Returned] = [Logical_Reads]/(case [Rows_Returned] when 0 then 1 else [Rows_Returned] end),
       [Execution_Count],
        CAST([Execution_Count] * 1.0 / SUM(case [Execution_Count]  when 0 then 1 else [Execution_Count] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Execution_Count_Percent],
       [Physical_Reads],
       CAST([Physical_Reads] * 1.0 / SUM(case [Physical_Reads] when 0 then 1 else [Physical_Reads] end ) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Physcal_Reads_Percent], 
       [Logical_Writes],
        CAST([Logical_Writes] * 1.0 / SUM(case [Logical_Writes] when 0 then 1 else [Logical_Writes] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Logical_Writes_Percent],
       [Logical_IO],
        CAST([Logical_IO] * 1.0 / SUM(case [Logical_IO] when 0 then 1 else [Logical_IO] end) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Logical_IO_Percent],
       [CLR_Time_MicroSec],
       CAST([CLR_Time_MicroSec] * 1.0 / SUM(case [CLR_Time_MicroSec] when 0 then 1 else [CLR_Time_MicroSec] end ) OVER() * 100.0 AS DECIMAL(5, 2)) AS [CLR_Time_Percent],
       [CPU_Time_Ms],[CPU_Time_Ms]/1000 [CPU_Time_Sec],
       [Duration_MicroSec],[Duration_MicroSec]/1000000 [Duration_Sec]
FROM DB_CPU_Stats
WHERE DatabaseID > 4 -- system databases
AND DatabaseID <> 32767 -- ResourceDB
ORDER BY [Rank_CPU] OPTION (RECOMPILE);
HakanM
fonte
0

Gostei tanto da consulta da CPU sys.dm_exec_query_statsque a ampliei. Ele ainda é classificado por CPU, mas adicionei outros totais e porcentagens para obter um melhor perfil de servidor. Isso copia bem no Excel e com formatação condicional de cores nas colunas Porcentagem, os piores números se destacam muito bem. Eu usei a 'Escala de cores graduadas' com 3 cores; uma cor de rosa para valores altos, amarelo para meio, verde para baixo.

Eu adicionei um rótulo para o database id 32676qual é o banco de dados interno de recursos SQL. Converto tempo de CPU e duração em horas para ter uma melhor noção do uso do tempo.

WITH DB_CPU_Stats
AS
(
    SELECT DatabaseID, isnull(DB_Name(DatabaseID),case DatabaseID when 32767 then 'Internal ResourceDB' else CONVERT(varchar(255),DatabaseID)end) AS [DatabaseName], 
      SUM(total_worker_time) AS [CPU Time Ms],
      SUM(total_logical_reads)  AS [Logical Reads],
      SUM(total_logical_writes)  AS [Logical Writes],
      SUM(total_logical_reads+total_logical_writes)  AS [Logical IO],
      SUM(total_physical_reads)  AS [Physical Reads],
      SUM(total_elapsed_time)  AS [Duration MicroSec],
      SUM(total_clr_time)  AS [CLR Time MicroSec],
      SUM(total_rows)  AS [Rows Returned],
      SUM(execution_count)  AS [Execution Count],
      count(*) 'Plan Count'

    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 [Rank CPU],
       DatabaseName,
       [CPU Time Hr] = convert(decimal(15,2),([CPU Time Ms]/1000.0)/3600) ,
        CAST([CPU Time Ms] * 1.0 / SUM([CPU Time Ms]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [CPU Percent],
       [Duration Hr] = convert(decimal(15,2),([Duration MicroSec]/1000000.0)/3600) , 
       CAST([Duration MicroSec] * 1.0 / SUM([Duration MicroSec]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Duration Percent],    
       [Logical Reads],
        CAST([Logical Reads] * 1.0 / SUM([Logical Reads]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Logical Reads Percent],      
       [Rows Returned],
        CAST([Rows Returned] * 1.0 / SUM([Rows Returned]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Rows Returned Percent],
       [Reads Per Row Returned] = [Logical Reads]/[Rows Returned],
       [Execution Count],
        CAST([Execution Count] * 1.0 / SUM([Execution Count]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Execution Count Percent],
       [Physical Reads],
       CAST([Physical Reads] * 1.0 / SUM([Physical Reads]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Physcal Reads Percent], 
       [Logical Writes],
        CAST([Logical Writes] * 1.0 / SUM([Logical Writes]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Logical Writes Percent],
       [Logical IO],
        CAST([Logical IO] * 1.0 / SUM([Logical IO]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [Logical IO Percent],
       [CLR Time MicroSec],
       CAST([CLR Time MicroSec] * 1.0 / SUM(case [CLR Time MicroSec] when 0 then 1 else [CLR Time MicroSec] end ) OVER() * 100.0 AS DECIMAL(5, 2)) AS [CLR Time Percent],
       [CPU Time Ms],[CPU Time Ms]/1000 [CPU Time Sec],
       [Duration MicroSec],[Duration MicroSec]/1000000 [Duration Sec]
FROM DB_CPU_Stats
--WHERE DatabaseID > 4 -- system databases
--AND DatabaseID <> 32767 -- ResourceDB
ORDER BY [Rank CPU] OPTION (RECOMPILE);
Drew Neff
fonte