Em um de nossos clientes, estamos enfrentando alguns problemas de desempenho em nosso aplicativo. É um aplicativo Web .NET 3.5 que está consumindo e atualizando dados em um banco de dados do SQL Server. Atualmente, nosso ambiente de produção consiste em um computador com Windows 2008 R2 como front-end e um cluster do SQL Server 2008 R2 no back-end. Nosso aplicativo usa COM + e MSDTC para se conectar ao banco de dados.
Aqui está o que está acontecendo: nossos usuários finais às vezes reclamam de lentidão no aplicativo. Algumas páginas levam mais tempo para carregar do que seria esperado. Enquanto tentava descobrir o que estava acontecendo, consegui descobrir algum comportamento estranho no lado do banco de dados que pode ser a causa da degradação do desempenho. Notei que, às vezes, existem algumas instruções SQL que levam muito mais tempo para serem executadas do que seria esperado. Consegui identificar algumas dessas instruções (principalmente são invocações de alguns dos procedimentos armazenados de nosso aplicativo) usando um rastreamento de criador de perfil (com o modelo TSQL_Duration) para identificar as consultas de execução longa.
O problema é que, quando eu executo esses procedimentos armazenados diretamente no banco de dados no SQL Management Studio, às vezes eles demoram muito (cerca de 7/8 segundos), outras vezes, são rápidos (menos de 1 segundo). Não sei por que isso acontece e está me deixando louco, porque a máquina SQL (4 núcleos, 32 GB) não está sendo usada por nenhum outro aplicativo e essas consultas não devem demorar muito para serem executadas.
Não sendo um DBA ou um guru do SQL Server, tenho tentado analisar algumas coisas que podem me ajudar a entender o problema. Aqui estão as etapas que eu tomei para tentar resolver o problema e o que descobri até agora:
- Todo o código TSQL chamado pelo aplicativo é gravado em procedimentos armazenados.
- Eu identifiquei algumas das consultas de longa execução no SQL Server Profiler, no entanto, quando as executo no Management Studio, elas demoram muito para serem executadas (de 4 a 10 segundos) ou são executadas rapidamente (menos de 1 segundo). Estou executando exatamente as mesmas consultas com os mesmos dados passados nos parâmetros. Essas consultas são principalmente procedimentos armazenados com instruções de seleção neles.
- Tentei examinar as estatísticas de espera e filas para tentar descobrir se existem processos aguardando alguns recursos. Eu executei a seguinte 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
Aqui está o que eu descobri:
- Depois de redefinir as estatísticas usando DBCC SQLPERF (cerca de 1 ou 2 horas depois), os tipos de espera que tenho mais são SOS_SCHEDULER_YIELD e WRITELOG
- Com o tempo (após aproximadamente 1 dia de execução), os tipos de espera que mais ocorrem no banco de dados são CXPACKET (67%) e OLEDB (17%), mesmo que o tempo médio de espera para cada um não seja longo. Também notei que as instruções de execução mais longa identificadas no SQL Profiler são chamadas para procedimentos armazenados que retornam mais de um conjunto de resultados (geralmente 3). Pode haver um problema de paralelismo aqui? Existe alguma maneira de tentar identificar se essa é a causa do problema?
- Li em algum lugar que o OLEDB espera pode ser causado por chamadas para recursos do OLEDB, como servidores vinculados. Temos um servidor vinculado para conectar-se a uma máquina dos Serviços de Indexação (MSIDXS), no entanto, nenhuma das instruções identificadas há muito tempo faz uso desse servidor vinculado.
- O tempo médio de espera mais alto que eu tenho é para esperas do tipo LCK_M_X (média de 1,5 segundos), mas esses tipos de espera não ocorrem com muita frequência em comparação com outros tipos (por exemplo, 64 LCK_M_X aguarda vs 10,823 CXPACKET aguarda no mesmo período de tempo )
- Uma coisa que notei é que o serviço MSDTC não está em cluster. O serviço SQL Server está em cluster, mas não o MSDTC. Pode haver um impacto no desempenho por causa disso? Estamos usando o MSDTC porque nosso aplicativo usa os Serviços Corporativos (DCOM) para acessar o banco de dados, mas os servidores não foram instalados e configurados por nós, mas por nosso cliente.
Alguém pode me ajudar a entender melhor esses dados? Alguém pode me ajudar a entender o que pode estar acontecendo? Existe algo que eu possa fazer no servidor para tentar descobrir as coisas? Devo falar com a equipe de desenvolvimento de aplicativos?
exec()
função explicaria o comportamento observado. Nesse caso, o usosp_executesql
normalmente resolve os problemas com instruções SQL dinâmicas.