Rendimiento del servidor SQL: PREEMPTIVE_OS_DELETESECURITYCONTEXT tipo de espera dominante

8

Ayer recibí una llamada de un cliente que se quejaba del uso elevado de la CPU en su SQL Server. Estamos utilizando SQL Server 2012 64 bit SE. El servidor ejecuta Windows Server 2008 R2 Standard, 2.20 GHz Intel Xeon (4 núcleos), 16 GB de RAM.

Después de asegurarme de que el culpable era de hecho SQL Server, eché un vistazo a las esperas superiores para la instancia usando la consulta DMV aquí . Las dos esperas principales fueron: (1) PREEMPTIVE_OS_DELETESECURITYCONTEXTy (2) SOS_SCHEDULER_YIELD.

EDITAR : Aquí están los resultados de la "consulta de espera superior" (aunque alguien reinició el servidor esta mañana en contra de mis deseos):

ingrese la descripción de la imagen aquí

Hacemos muchos cálculos / conversiones intensas, así que puedo entender SOS_SCHEDULER_YIELD. Sin embargo, tengo mucha curiosidad sobre el PREEMPTIVE_OS_DELETESECURITYCONTEXTtipo de espera y por qué podría ser el más alto.

La mejor descripción / discusión que puedo encontrar sobre este tipo de espera se puede encontrar aquí . Menciona:

Los tipos de espera PREEMPTIVE_OS_ son llamadas que abandonaron el motor de la base de datos, generalmente a una API Win32, y realizan código fuera de SQL Server para diversas tareas. En este caso, está eliminando un contexto de seguridad utilizado anteriormente para el acceso a recursos remotos. La API relacionada en realidad se llama DeleteSecurityContext ()

Que yo sepa, no tenemos recursos externos como servidores vinculados o tablas de archivos. Y no hacemos ninguna suplantación, etc. ¿Podría una copia de seguridad haber causado que esto aumentara o tal vez un controlador de dominio defectuoso?

¿Qué diablos podría hacer que este sea el tipo de espera dominante? ¿Cómo puedo seguir este tipo de espera?

Edición 2: Revisé el contenido del Registro de seguridad de Windows. Veo algunas entradas que pueden ser de interés, pero no estoy seguro de si son normales:

Special privileges assigned to new logon.

Subject:
    Security ID:        NT SERVICE\MSSQLServerOLAPService
    Account Name:       MSSQLServerOLAPService
    Account Domain:     NT Service
    Logon ID:       0x3143c

Privileges:     SeImpersonatePrivilege

Special privileges assigned to new logon.

Subject:
    Security ID:        NT SERVICE\MSSQLSERVER
    Account Name:       MSSQLSERVER
    Account Domain:     NT Service
    Logon ID:       0x2f872

Privileges:     SeAssignPrimaryTokenPrivilege
            SeImpersonatePrivilege

Edición 3 : @Jon Seigel, como solicitó, aquí están los resultados de su consulta. Un poco diferente al de Paul:

ingrese la descripción de la imagen aquí

Edición 4: Lo admito, soy el primer usuario de Extended Events. Agregué este tipo de espera al evento wait_info_external y vi cientos de entradas. No hay texto sql o identificador de plan, solo una pila de llamadas. ¿Cómo puedo seguir rastreando la fuente?

ingrese la descripción de la imagen aquí

John Russell
fuente
John, ¿puedes ejecutar sp_whoisactive por un período de tiempo (tal vez un minuto) y ver qué aparece? Esto puede ayudarlo a dirigirnos a una solución. sqlblog.com/files/default.aspx
Mark Wilkinson
Hola John, en tu pregunta mencionas haber identificado a SQL Server como el culpable. ¿Podría por favor describir los pasos que tomó para llegar a esa conclusión?
Craig Efrein

Respuestas:

3

Sé que esta pregunta, basada en el Título, se refiere principalmente al tipo de espera PREEMPTIVE_OS_DELETESECURITYCONTEXT, pero creo que es una mala dirección del verdadero problema que es " un cliente que se quejaba de un alto uso de CPU en su servidor SQL ".

La razón por la que creo que centrarse en este tipo de espera específico es una búsqueda inútil es porque aumenta con cada conexión realizada. Estoy ejecutando la siguiente consulta en mi computadora portátil (lo que significa que soy el único usuario):

SELECT * 
FROM sys.dm_os_wait_stats
WHERE wait_type = N'PREEMPTIVE_OS_DELETESECURITYCONTEXT'

Y luego hago lo siguiente y vuelvo a ejecutar esta consulta:

  • abrir una nueva pestaña de consulta
  • cierre la nueva pestaña de consulta
  • ejecute lo siguiente desde un indicador de DOS: SQLCMD -E -Q "select 1"

Ahora, sabemos que la CPU es alta, por lo que debemos ver qué se está ejecutando para ver qué sesiones tienen una CPU alta:

SELECT req.session_id AS [SPID],
       req.blocking_session_id AS [BlockedBy],
       req.logical_reads AS [LogReads],
       DB_NAME(req.database_id) AS [DatabaseName],
       SUBSTRING(txt.[text],
                 (req.statement_start_offset / 2) + 1,
                 CASE
                     WHEN req.statement_end_offset > 0
                        THEN (req.statement_end_offset - req.statement_start_offset) / 2
                     ELSE LEN(txt.[text])
                 END
                ) AS [CurrentStatement],
       txt.[text] AS [CurrentBatch],
       CONVERT(XML, qplan.query_plan) AS [StatementQueryPlan],
       OBJECT_NAME(qplan.objectid, qplan.[dbid]) AS [ObjectName],
       sess.[program_name],
       sess.[host_name],
       sess.nt_user_name,
       sess.total_scheduled_time,
       sess.memory_usage,
       req.*
FROM sys.dm_exec_requests req
INNER JOIN sys.dm_exec_sessions sess
        ON sess.session_id = req.session_id
CROSS APPLY sys.dm_exec_sql_text(req.[sql_handle]) txt
OUTER APPLY sys.dm_exec_text_query_plan(req.plan_handle,
                                        req.statement_start_offset,
                                        req.statement_end_offset) qplan
WHERE req.session_id <> @@SPID
ORDER BY req.logical_reads DESC, req.cpu_time DESC
--ORDER BY req.cpu_time DESC, req.logical_reads DESC

Normalmente ejecuto la consulta anterior tal como está, pero también podría cambiar qué cláusula ORDER BY se comenta para ver si eso da resultados más interesantes / útiles.

Alternativamente, puede ejecutar lo siguiente, basado en dm_exec_query_stats, para encontrar las consultas de mayor costo. La primera consulta a continuación le mostrará consultas individuales (incluso si tienen varios planes) y está ordenada por Tiempo promedio de CPU, pero puede cambiarla fácilmente para que sea Lecturas lógicas promedio. Una vez que encuentre una consulta que parece que está tomando muchos recursos, copie el "sql_handle" y el "Statement_start_offset" en la condición WHERE de la segunda consulta a continuación para ver los planes individuales (puede ser más de 1). Desplácese hacia el extremo derecho y suponiendo que haya un plan XML, se mostrará como un enlace (en modo de cuadrícula) que lo llevará al visor del plan si hace clic en él.

Consulta # 1: Obtener información de consulta

;WITH cte AS
(
   SELECT qstat.[sql_handle],
          qstat.statement_start_offset,
          qstat.statement_end_offset,
          COUNT(*) AS [NumberOfPlans],
          SUM(qstat.execution_count) AS [TotalExecutions],

          SUM(qstat.total_worker_time) AS [TotalCPU],
          (SUM(qstat.total_worker_time * 1.0) / SUM(qstat.execution_count)) AS [AvgCPUtime],
          MAX(qstat.max_worker_time) AS [MaxCPU],

          SUM(qstat.total_logical_reads) AS [TotalLogicalReads],
   (SUM(qstat.total_logical_reads * 1.0) / SUM(qstat.execution_count)) AS [AvgLogicalReads],
          MAX(qstat.max_logical_reads) AS [MaxLogicalReads],

          SUM(qstat.total_rows) AS [TotalRows],
          (SUM(qstat.total_rows * 1.0) / SUM(qstat.execution_count)) AS [AvgRows],
          MAX(qstat.max_rows) AS [MaxRows]
   FROM sys.dm_exec_query_stats  qstat
   GROUP BY qstat.[sql_handle], qstat.statement_start_offset, qstat.statement_end_offset
)
SELECT  cte.*,
        DB_NAME(txt.[dbid]) AS [DatabaseName],
        SUBSTRING(txt.[text],
                  (cte.statement_start_offset / 2) + 1,
                  CASE
                      WHEN cte.statement_end_offset > 0
                          THEN (cte.statement_end_offset - cte.statement_start_offset) / 2
                      ELSE LEN(txt.[text])
                  END
                 ) AS [CurrentStatement],
        txt.[text] AS [CurrentBatch]
FROM cte
CROSS APPLY sys.dm_exec_sql_text(cte.[sql_handle]) txt
ORDER BY cte.AvgCPUtime DESC

Consulta # 2: Obtener información del plan

SELECT  *,
        DB_NAME(qplan.[dbid]) AS [DatabaseName],
        CONVERT(XML, qplan.query_plan) AS [StatementQueryPlan],
        SUBSTRING(txt.[text],
                  (qstat.statement_start_offset / 2) + 1,
                  CASE
                        WHEN qstat.statement_end_offset > 0
                        THEN (qstat.statement_end_offset - qstat.statement_start_offset) / 2
                        ELSE LEN(txt.[text])
                  END
                 ) AS [CurrentStatement],
        txt.[text] AS [CurrentBatch]
FROM sys.dm_exec_query_stats  qstat
CROSS APPLY sys.dm_exec_sql_text(qstat.[sql_handle]) txt
OUTER APPLY sys.dm_exec_text_query_plan(qstat.plan_handle,
                                        qstat.statement_start_offset,
                                        qstat.statement_end_offset) qplan
-- paste info from Query #1 below
WHERE qstat.[sql_handle] = 0x020000001C70C614D261C85875D4EF3C90BD18D02D62453800....
AND qstat.statement_start_offset = 164
-- paste info from Query #1 above
ORDER BY qstat.total_worker_time DESC
Solomon Rutzky
fuente
Pensé que el tipo de espera mejor clasificado del script de Paul, es decir PREEMPTIVE_OS_DELETESECURITYCONTEXT, podría ser la causa de la alta CPU. ¿Puede esto ser considerado un tipo de espera benigno en nuestro caso? En nuestra aplicación, tenemos algunos servicios de Windows que constantemente envían comandos (ejecutados procesados ​​por exec) a SQL Server. No puedo distinguir demasiados patrones de sys.dm_exec_sessions: las sesiones no permanecen abiertas demasiado tiempo y hay muchas. sys.dm_exec_query_stats proporciona buena información sobre los procesos almacenados más caros en cuanto al costo total de la CPU. Este puede ser un buen lugar para comenzar.
John Russell
Solo quería asegurarme de que no me faltaba algo con PREEMPTIVE_OS_DELETESECURITYCONTEXT. ¿No sabía si esto podría rastrearse a un controlador de dominio defectuoso o búsquedas AD?
John Russell
@JohnRussell: Creo que el tipo de espera más alto suele ser un buen lugar para comenzar, pero mi punto es que este en particular no solo se activa por código dentro de SQL Server que accede a recursos externos, como Linked Server o SQLCLR o procesos almacenados extendidos (p. Ej. xp_dirtree), por lo tanto, el alto volumen no es un verdadero indicador. E incluso si hay una latencia de red que causa demoras, ¿eso realmente elevaría la CPU o simplemente aumentaría el bloqueo? Y buen punto, use query_stats. Actualizaré mi consulta más tarde con eso.
Solomon Rutzky
1
@JohnRussell: con respecto a sus "servicios de Windows que constantemente envían comandos", ¿ha cambiado algo recientemente? ¿Están cerrando adecuadamente las conexiones? ¿Limpian correctamente la conexión si se producen errores mientras están conectados? Además, ¿ha reconstruido los índices recientemente o al menos ha actualizado las estadísticas en todas las tablas? No hacer eso podría conducir a un aumento de la CPU.
Solomon Rutzky
Gracias por la idea! A medida que miro más de cerca sys.dm_exec_query_stats y la fragmentación del índice en algunas tablas clave, empiezo a sentirme más seguro acerca de la causa. PREEMPTIVE_OS_DELETESECURITYCONTEXT simplemente me echó.
John Russell
1

SecurityContext es utilizado por el servidor sql en varios lugares. Un ejemplo que ha nombrado son los servidores vinculados y las tablas de archivos. ¿Quizás estás usando cmdexec? ¿Trabajos del Agente SQL Server con cuentas proxy? ¿Llamar a un servicio web? Los recursos remotos pueden ser muchas cosas divertidas.

Los eventos de suplantación pueden registrarse en el evento de seguridad de Windows. Podría ser que estás encontrando una pista allí. Además, es posible que desee comprobar la grabadora de blackbox, también conocida como eventos extendidos.

¿Ha verificado si estos tipos de espera son nuevos (y en conexión con la CPU alta) o si son normales para su servidor?

Jens W.
fuente
No tenemos trabajos de Agente SQL Server ni Servicios Web. Borré las estadísticas de espera y volví a ejecutar la consulta original anterior y resurgen estadísticas similares. Me tomó un tiempo descubrir cómo reconfigurar la sesión de evento extendida system_health para incluir wait_info_external for waittype = 'PREEMPTIVE_OS_DELETESECURITYCONTEXT', pero finalmente lo agregué, puedo ver cientos de estos eventos en los pocos segundos que estuve observando los datos en vivo . Estoy viendo cómo descifrar mejor la fuente. ¿Algún consejo sobre cómo rastrear esto?
John Russell