En uno de nuestros clientes, hemos tenido algunos problemas de rendimiento en nuestra aplicación. Es una aplicación web .NET 3.5 que consume y actualiza datos en una base de datos de SQL Server. Actualmente, nuestro entorno de producción consiste en una máquina con Windows 2008 R2 como front-end y un clúster de SQL Server 2008 R2 en el back-end. Nuestra aplicación utiliza COM + y MSDTC para conectarse a la base de datos.
Esto es lo que sucede: nuestros usuarios finales a veces se quejan de la lentitud en la aplicación. Algunas páginas tardan más en cargarse de lo esperado. Mientras intentaba averiguar qué estaba pasando, logré descubrir un comportamiento extraño en el lado de la base de datos que puede ser la causa de la degradación del rendimiento. Me di cuenta de que a veces hay algunas instrucciones SQL que tardan mucho más en ejecutarse de lo que se esperaría. Logré identificar algunas de estas declaraciones (principalmente son invocaciones de algunos de los procedimientos almacenados de nuestra aplicación) usando una traza de perfil (con la plantilla TSQL_Duration) para identificar las consultas de larga duración.
El problema es que cuando ejecuto estos procedimientos almacenados directamente en la base de datos en SQL Management Studio, a veces tardan mucho (aproximadamente 7/8 segundos), otras veces son rápidos (menos de 1 segundo). No sé por qué sucede esto y me está volviendo loco, porque la máquina SQL (4 núcleos, 32 GB) no está siendo utilizada por ninguna otra aplicación, y estas consultas no deberían tardar tanto tiempo en ejecutarse.
Al no ser un DBA o un gurú de SQL Server, he estado tratando de ver algunas cosas que pueden ayudarme a comprender el problema. Estos son los pasos que he tomado para tratar de resolver el problema y lo que descubrí hasta ahora:
- Todo el código TSQL llamado por la aplicación está escrito en procedimientos almacenados.
- Identifiqué algunas de las consultas de larga ejecución en el perfilador de SQL Server, sin embargo, cuando las ejecuto en Management Studio, tardan mucho en ejecutarse (de 4 a 10 segundos) o se ejecutan rápidamente (menos de 1 segundo). Estoy ejecutando exactamente las mismas consultas con los mismos datos pasados en los parámetros. Estas consultas son principalmente procedimientos almacenados con sentencias select en ellos.
- Intenté mirar las estadísticas de espera y colas para tratar de averiguar si hay procesos esperando algunos recursos. Ejecuté la siguiente consulta:
WITH Waits AS
(SELECT
wait_type,
wait_time_ms / 1000.0 AS WaitS,
(wait_time_ms - signal_wait_time_ms) / 1000.0 AS ResourceS,
signal_wait_time_ms / 1000.0 AS SignalS,
waiting_tasks_count AS WaitCount,
100.0 * wait_time_ms / SUM (wait_time_ms) OVER() AS Percentage,
ROW_NUMBER() OVER(ORDER BY wait_time_ms DESC) AS RowNum
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
'CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE', 'SLEEP_TASK',
'SLEEP_SYSTEMTASK', 'SQLTRACE_BUFFER_FLUSH', 'WAITFOR', 'LOGMGR_QUEUE',
'CHECKPOINT_QUEUE', 'REQUEST_FOR_DEADLOCK_SEARCH', 'XE_TIMER_EVENT', 'BROKER_TO_FLUSH',
'BROKER_TASK_STOP', 'CLR_MANUAL_EVENT', 'CLR_AUTO_EVENT', 'DISPATCHER_QUEUE_SEMAPHORE',
'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT', 'XE_DISPATCHER_JOIN', 'BROKER_EVENTHANDLER',
'TRACEWRITE', 'FT_IFTSHC_MUTEX', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'BROKER_RECEIVE_WAITFOR', 'ONDEMAND_TASK_QUEUE', 'DBMIRROR_EVENTS_QUEUE',
'DBMIRRORING_CMD', 'BROKER_TRANSMITTER', 'SQLTRACE_WAIT_ENTRIES',
'SLEEP_BPOOL_FLUSH', 'SQLTRACE_LOCK')
)
SELECT
W1.wait_type AS WaitType,
CAST (W1.WaitS AS DECIMAL(14, 2)) AS Wait_S,
CAST (W1.ResourceS AS DECIMAL(14, 2)) AS Resource_S,
CAST (W1.SignalS AS DECIMAL(14, 2)) AS Signal_S,
W1.WaitCount AS WaitCount,
CAST (W1.Percentage AS DECIMAL(4, 2)) AS Percentage,
CAST ((W1.WaitS / W1.WaitCount) AS DECIMAL (14, 4)) AS AvgWait_S,
CAST ((W1.ResourceS / W1.WaitCount) AS DECIMAL (14, 4)) AS AvgRes_S,
CAST ((W1.SignalS / W1.WaitCount) AS DECIMAL (14, 4)) AS AvgSig_S
FROM Waits AS W1
INNER JOIN Waits AS W2 ON W2.RowNum <= W1.RowNum
GROUP BY W1.RowNum, W1.wait_type, W1.WaitS, W1.ResourceS, W1.SignalS, W1.WaitCount, W1.Percentage
HAVING SUM (W2.Percentage) - W1.Percentage < 95; -- percentage threshold
GO
Esto es lo que descubrí:
- Después de restablecer las estadísticas usando DBCC SQLPERF (aproximadamente 1 o 2 horas después), los tipos de espera que más tengo son SOS_SCHEDULER_YIELD y WRITELOG
- Con el tiempo (después de aproximadamente 1 día de ejecución), los tipos de espera que ocurren más en la base de datos son CXPACKET (67%) y OLEDB (17%), aunque el tiempo de espera promedio para cada uno no es largo. También noté que las declaraciones más largas identificadas en SQL Profiler son llamadas a procedimientos almacenados que devuelven más de un conjunto de resultados (a menudo 3). ¿Puede haber un problema de paralelismo aquí? ¿Hay alguna forma de tratar de identificar si esta es la causa del problema?
- He leído en alguna parte que las esperas OLEDB pueden ser causadas por llamadas a recursos OLEDB como servidores vinculados. Tenemos un servidor vinculado para conectarse con una máquina de Indexing Services (MSIDXS), sin embargo, ninguna de las declaraciones identificadas como de larga duración hace uso de ese servidor vinculado.
- El tiempo de espera promedio más alto que tengo es para las esperas de tipo LCK_M_X (aproximadamente 1.5 segundos promedio), pero estos tipos de espera no ocurren con mucha frecuencia en comparación con otros tipos (por ejemplo, 64 esperas de LCK_M_X frente a 10.823 esperas de CXPACKET en el mismo período de tiempo )
- Una cosa que noté es que el servicio MSDTC no está agrupado. El servicio SQL Server está agrupado pero no MSDTC. ¿Puede haber un éxito en el rendimiento debido a esto? Estamos usando MSDTC porque nuestra aplicación usa Enterprise Services (DCOM) para acceder a la base de datos, pero los servidores no fueron instalados y configurados por nosotros, sino por nuestro cliente.
¿Alguien puede ayudarme a dar más sentido a estos datos? ¿Alguien puede ayudarme a comprender lo que puede estar sucediendo? ¿Hay algo que pueda hacer en el servidor para tratar de resolver las cosas? ¿Debo hablar con el equipo de desarrollo de aplicaciones?
exec()
función explicaría el comportamiento observado. En este caso, el usosp_executesql
normalmente resuelve los problemas con las sentencias SQL dinámicas.