¿Cómo obtener el uso de CPU por base de datos para una instancia particular?

15

He encontrado las siguientes consultas para detectar el uso de CPU por base de datos, pero muestran 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);

La consulta anterior indica que el problema es con una de mi base de datos (casi el 96%).

Y la consulta a continuación dice que el problema es con el maestro y las bases de datos de distribución (aproximadamente el 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

He comprobado la Thet sys.sysprocessesse decpreated. ¿Significa esto que los resultados de la segunda consulta son incorrectos?

gotqn
fuente

Respuestas:

14

Si bien, al igual que @Thomas, estoy completamente de acuerdo con @Aaron en los comentarios sobre la pregunta sobre si el uso de CPU por base de datos es preciso o útil, al menos puedo responder a la pregunta de por qué esas dos consultas son tan diferente. Y la razón por la cual son diferentes indicará cuál es más preciso, aunque ese mayor nivel de precisión sigue siendo relativo al que es específicamente inexacto, por lo tanto, aún no es realmente preciso ;-).

La primera consulta usa sys.dm_exec_query_stats para obtener información de la CPU (es decir total_worker_time). Si va a la página vinculada que es la documentación de MSDN para ese DMV, verá una breve introducción de 3 oraciones y 2 de esas oraciones nos dan la mayor parte de lo que necesitamos para comprender el contexto de esta información ("cuán confiable es" y "cómo se compara con sys.sysprocesses"). Esas dos oraciones son:

Devuelve estadísticas de rendimiento agregado para planes de consulta en caché en SQL Server. ... Cuando se elimina un plan de la memoria caché, las filas correspondientes se eliminan de esta vista

La primera oración, "Devuelve estadísticas de rendimiento agregado ", nos dice que la información en este DMV (al igual que muchas otras) es acumulativa y no específica para solo las consultas que se están ejecutando actualmente. Esto también se indica mediante un campo en ese DMV que no forma parte de la consulta en la Pregunta execution_count, que nuevamente muestra que se trata de datos acumulativos. Y es bastante útil que estos datos sean acumulativos, ya que puede obtener promedios, etc., dividiendo algunas de las métricas por execution_count.

La segunda oración, "los planes que se eliminan del caché también se eliminan de este DMV", indican que no es una imagen completa en absoluto, especialmente si el servidor ya tiene un caché de plan bastante completo y está bajo carga y, por lo tanto, está caducando los planes algo frecuente Además, la mayoría de los DMV se restablecen cuando el servidor se reinicia, por lo que no son un historial real, incluso si estas filas no se eliminaron cuando caducan los planes.

Ahora vamos a contrastar lo anterior con sys.sysprocesses. Esta vista del sistema solo muestra lo que se está ejecutando actualmente, al igual que la combinación de sys.dm_exec_connections , sys.dm_exec_sessions y sys.dm_exec_requests (que se indica en la página vinculada para sys.dm_exec_sessions). Esta es una vista completamente diferente del servidor en comparación con el sys.dm_exec_query_statsDMV que contiene los datos incluso después de que se complete el proceso. Es decir, en relación con "¿son incorrectos los resultados de la segunda consulta?" pregunta, no están equivocados, solo pertenecen a un aspecto diferente (es decir, el marco temporal) de las estadísticas de rendimiento.

Entonces, la consulta que usa sys.sysprocessessolo está mirando "en este momento". Y la consulta que usa sys.dm_exec_query_statsestá mirando principalmente (tal vez) lo que ha sucedido desde el último reinicio del servicio SQL Server (o obviamente reinicio del sistema). Para el análisis de rendimiento general parece que sys.dm_exec_query_statses mucho mejor, pero nuevamente, deja caer información útil todo el tiempo. Y, en ambos casos, también debe considerar los puntos hechos por @Aaron en los comentarios de la pregunta (ya eliminados) con respecto a la precisión del valor "database_id" en primer lugar (es decir, solo refleja la base de datos activa que inició el código) , no necesariamente donde está ocurriendo el "problema").

Pero, si lo que necesita / quiere tener una idea de lo que está sucediendo ahora mismo en todas las bases de datos, posiblemente porque las cosas se están desacelerando en este momento, es mejor usar la combinación de sys.dm_exec_connections, sys.dm_exec_sessionsy sys.dm_exec_requests(y no el obsoleto sys.sysprocesses). Solo tenga en cuenta que está buscando / para consultas , no para bases de datos , porque las consultas pueden unirse a través de múltiples bases de datos, incluir UDF de una o más bases de datos, etc.


EDITAR:
si la preocupación general es reducir el consumo elevado de CPU, busque las consultas que ocupan más CPU, porque las bases de datos en realidad no ocupan CPU (la búsqueda por base de datos podría funcionar en una empresa de alojamiento donde cada base de datos está aislada y propiedad de un cliente diferente).

La siguiente consulta ayudará a identificar consultas con un uso de CPU promedio alto. Condensa los datos en el DMV query_stats ya que esos registros pueden mostrar la misma consulta (sí, el mismo subconjunto del lote de consulta) varias veces, cada una con un plan de ejecución 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
fuente
está AvgCPUen milisegundos?
Kolob Canyon
Hola @KolobCanyon. De acuerdo con la documentación de sys.dm_exec_query_stats , total_worker_timees " Cantidad total de tiempo de CPU, reportada en microsegundos (pero solo precisa a milisegundos), que fue consumida por las ejecuciones de este plan desde que se compiló ". ¿Eso ayuda? Eso podría convertirse fácilmente en milisegundos si eso es lo que quieres ver.
Solomon Rutzky
1

Ajusté la consulta para la división por 0 errores y optimicé los nombres de columna para copiar / pegar en 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
fuente
0

Me gustó tanto la consulta de la CPU sys.dm_exec_query_statsque la extendí. Todavía está clasificado por CPU, pero agregué otros totales y porcentajes para obtener un mejor perfil de servidor. Esto se copia muy bien en Excel y con el formato de color condicional en las columnas de Porcentaje, los peores números se destacan muy bien. Usé la 'Escala de colores graduada' con 3 colores; un color rosa para valores altos, amarillo para el medio, verde para bajo.

Agregué una etiqueta para la database id 32676cual es la base de datos interna de recursos SQL. Convierto el tiempo de CPU y Duración en Horas para tener una mejor idea del uso del tiempo.

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
fuente