Isso é um sintoma de um servidor sobrecarregado?

12

Eu tenho tentado diagnosticar lentidão em um aplicativo. Para isso, registrei os eventos estendidos do SQL Server .

  • Para esta pergunta, estou analisando um procedimento armazenado específico.
  • Mas há um conjunto principal de uma dúzia de procedimentos armazenados que podem igualmente ser usados ​​como uma investigação de maçãs para maçãs
  • e sempre que executo manualmente um dos procedimentos armazenados, ele sempre é executado rapidamente
  • e se um usuário tentar novamente: ele será executado rapidamente.

Os tempos de execução do procedimento armazenado variam muito. Muitas das execuções deste procedimento armazenado retornam em <1s:

insira a descrição da imagem aqui

E para esse balde "rápido" , é muito menos que 1s. Na verdade, é cerca de 90 ms:

insira a descrição da imagem aqui

Mas há uma longa fila de usuários que precisam esperar 2s, 3s, 4s segundos. Alguns têm que esperar 12s, 13s, 14s. Depois, há as almas realmente pobres que precisam esperar 22s, 23s, 24s.

E após 30 anos, o aplicativo cliente desiste, anula a consulta e o usuário teve que esperar 30 segundos .

Correlação para encontrar a causa

Então, eu tentei correlacionar:

  • duração versus leituras lógicas
  • duração versus leituras físicas
  • duration vs tempo da CPU

E nenhum parece dar correlação; nenhum parece ser a causa

  • duration vs. leituras lógicas : se são poucas ou muitas leituras lógicas, a duração ainda varia muito :

    insira a descrição da imagem aqui

  • duration vs. leituras físicas : mesmo que a consulta não tenha sido atendida no cache e muitas leituras físicas sejam necessárias, isso não afeta a duração:

    insira a descrição da imagem aqui

  • duration vs cpu time : se a consulta levou 0s de tempo da CPU ou 2,5s completos de tempo de CPU, as durações têm a mesma variabilidade:

    insira a descrição da imagem aqui

Bônus : notei que a duração v leituras físicas e duração v tempo da CPU são muito semelhantes. Isso é comprovado se eu tentar correlacionar o tempo da CPU com as leituras físicas:

insira a descrição da imagem aqui

Acontece que muito uso da CPU vem de E / S. Quem sabia!

Portanto, se não há nada sobre o ato de executar a consulta que possa explicar as diferenças no tempo de execução, isso implica que há algo não relacionado à CPU ou ao disco rígido?

Se a CPU ou o disco rígido foram o gargalo; não seria o gargalo?

Se supusermos que era a CPU que era o gargalo; que a CPU está com pouca energia para este servidor:

  • as execuções usando mais tempo de CPU não levariam mais tempo?
  • já que eles precisam concluir com outras pessoas usando a CPU sobrecarregada?

Da mesma forma para os discos rígidos. Se pensarmos que o disco rígido era um gargalo; que os discos rígidos não têm throughput aleatório suficiente para este servidor:

  • as execuções usando leituras mais físicas não levariam mais tempo?
  • já que eles precisam concluir com outras pessoas usando a E / S do disco rígido sobrecarregada?

O procedimento armazenado em si não executa nem exige nenhuma gravação.

  • Normalmente, ele retorna 0 linhas (90%).
  • Ocasionalmente, ele retornará 1 linha (7%).
  • Raramente ele retornará 2 linhas (1,4%).
  • E, nos piores casos, retornou mais de 2 linhas (uma vez retornando 12 linhas)

Portanto, não é como se estivesse retornando um volume insano de dados.

Uso da CPU do servidor

O uso do processador do servidor é em média de 1,8%, com um pico ocasional de até 18% - portanto, não parece que a carga da CPU seja um problema:

insira a descrição da imagem aqui

Portanto, a CPU do servidor não parece sobrecarregada.

Mas o servidor é virtual ...

Algo fora do universo?

A única coisa que posso imaginar é algo que existe fora do universo do servidor.

  • se não for leituras lógicas
  • e não é leituras físicas
  • e não é uso de CPU
  • e não é carga da CPU

E não são os parâmetros do procedimento armazenado (porque emitir a mesma consulta manualmente e não leva 27 segundos - leva ~ 0 segundos).

O que mais poderia explicar o servidor às vezes levando 30 segundos, em vez de 0 segundos, para executar o mesmo procedimento armazenado compilado.

  • pontos de verificação?

É um servidor virtual

  • o host sobrecarregado?
  • outra VM no mesmo host?

Passando pelos eventos estendidos do servidor; não há mais nada acontecendo particularmente quando uma consulta leva 20 segundos repentinamente. Ele roda bem e decide não rodar bem:

  • 2 segundos
  • 1 segundo
  • 30 segundos
  • 3 segundos
  • 2 segundos

E não há outros itens particularmente extenuantes que eu possa encontrar. Não é durante o backup do log de transações a cada 2 horas.

O que mais poderia ser?

Existe algo que eu possa dizer além: "o servidor" ?

Editar : correlacionar por hora do dia

Percebi que correlacionei as durações a tudo:

  • leituras lógicas
  • leituras físicas
  • utilização do CPU

Mas a única coisa com a qual não correlacionei foi a hora do dia . Talvez o backup do log de transações a cada duas horas seja um problema.

Ou talvez a lentidão que ocorrem em mandris durante checkpoints?

Não:

insira a descrição da imagem aqui

Intel Xeon Gold Quad-core 6142.

Editar - as pessoas estão colocando a hipótese do plano de execução da consulta

As pessoas estão com a hipótese de que os planos de execução da consulta devem ser diferentes entre "rápido" e "lento". Eles não são.

E podemos ver isso imediatamente a partir da inspeção.

Sabemos que a duração mais longa da pergunta não se deve a um plano de execução "ruim":

  • aquele que fez leituras mais lógicas
  • um que consumiu mais CPU com mais junções e pesquisas de chave

Como se um aumento nas leituras ou um aumento na CPU fosse uma causa do aumento da duração da consulta, já teríamos visto isso acima. Não há correlação.

Mas vamos tentar correlacionar a duração com a métrica do produto da área de leitura da CPU:

insira a descrição da imagem aqui

Torna-se ainda menos correlação - o que é um paradoxo.


Editar : atualizados os diagramas de dispersão para solucionar um erro nos gráficos de dispersão do Excel com um grande número de valores.

Próximos passos

Minhas próximas etapas serão conseguir que alguém tenha que gerar eventos para consultas bloqueadas no servidor - após 5 segundos:

EXEC sp_configure 'blocked process threshold', '5';
RECONFIGURE

Não explica se as consultas são bloqueadas por 4 segundos. Mas talvez qualquer coisa que esteja bloqueando uma consulta por 5 segundos também bloqueie algumas por 4 segundos.

Os planos lentos

Aqui está o plano lento dos dois procedimentos armazenados sendo executados:

  • `` EXECUTE FindFrob @CustomerID = 7383, @StartDate = '20190725 04: 00: 00.000', @EndDate = '20190726 04: 00: 00.000'
  • `` EXECUTE FindFrob @CustomerID = 7383, @StartDate = '20190725 04: 00: 00.000', @EndDate = '20190726 04: 00: 00.000'

O mesmo procedimento armazenado, com os mesmos parâmetros, é executado consecutivamente:

| Duration (us) | CPU time (us) | Logical reads | Physical reads | 
|---------------|---------------|---------------|----------------|
|    13,984,446 |        47,000 |         5,110 |            771 |
|     4,603,566 |        47,000 |         5,126 |            740 |

Ligue 1:

|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Contoso2].[dbo].[Frobs].[FrobGUID]) OPTIMIZED)
    |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]))
    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[RowNumber]) OPTIMIZED)
    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
    |    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
    |    |    |    |    |--Index Seek(OBJECT:([Contoso2].[dbo].[TransactionPatronInfo].[IX_TransactionPatronInfo_CustomerID_TransactionGUID] AS [tpi]), SEEK:([tpi].[CustomerID]=[@CustomerID]) ORDERED FORWARD)
    |    |    |    |    |--Index Seek(OBJECT:([Contoso2].[dbo].[Transactions].[IX_Transactions_TransactionGUIDTransactionDate]), SEEK:([Contoso2].[dbo].[Transactions].[TransactionGUID]=[Contoso2].[dbo
    |    |    |    |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions2_MoneyAppearsOncePerTransaction]), SEEK:([Contoso2].[dbo].[FrobTransactions].[TransactionGUID]=[Contos
    |    |    |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions_RowNumber]), SEEK:([Contoso2].[dbo].[FrobTransactions].[RowNumber]=[Contoso2].[dbo].[Fin
    |    |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[Frobs].[PK_Frobs_FrobGUID]), SEEK:([Contoso2].[dbo].[Frobs].[FrobGUID]=[Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]),  WHERE:([Contos
    |--Filter(WHERE:([Expr1009]>(1)))
     |--Compute Scalar(DEFINE:([Expr1009]=CONVERT_IMPLICIT(int,[Expr1012],0)))
          |--Stream Aggregate(DEFINE:([Expr1012]=Count(*)))
           |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactins_OnFrobGUID]), SEEK:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]=[Contoso2].[dbo].[Frobs].[LC

Chamada 2

|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Contoso2].[dbo].[Frobs].[FrobGUID]) OPTIMIZED)
    |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]))
    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[RowNumber]) OPTIMIZED)
    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
    |    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
    |    |    |    |    |--Index Seek(OBJECT:([Contoso2].[dbo].[TransactionPatronInfo].[IX_TransactionPatronInfo_CustomerID_TransactionGUID] AS [tpi]), SEEK:([tpi].[CustomerID]=[@CustomerID]) ORDERED FORWARD)
    |    |    |    |    |--Index Seek(OBJECT:([Contoso2].[dbo].[Transactions].[IX_Transactions_TransactionGUIDTransactionDate]), SEEK:([Contoso2].[dbo].[Transactions].[TransactionGUID]=[Contoso2].[dbo
    |    |    |    |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions2_MoneyAppearsOncePerTransaction]), SEEK:([Contoso2].[dbo].[FrobTransactions].[TransactionGUID]=[Contos
    |    |    |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions_RowNumber]), SEEK:([Contoso2].[dbo].[FrobTransactions].[RowNumber]=[Contoso2].[dbo].[Fin
    |    |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[Frobs].[PK_Frobs_FrobGUID]), SEEK:([Contoso2].[dbo].[Frobs].[FrobGUID]=[Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]),  WHERE:([Contos
    |--Filter(WHERE:([Expr1009]>(1)))
     |--Compute Scalar(DEFINE:([Expr1009]=CONVERT_IMPLICIT(int,[Expr1012],0)))
          |--Stream Aggregate(DEFINE:([Expr1012]=Count(*)))
           |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactins_OnFrobGUID]), SEEK:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]=[Contoso2].[dbo].[Frobs].[LC

Faz sentido que os planos sejam idênticos; está executando o mesmo procedimento armazenado, com os mesmos parâmetros.

Ian Boyd
fonte
2
Você pode postar os planos de consulta - boa execução versus má execução?
Kin Shah
4
Meu primeiro palpite a partir daqui seria o bloqueio ...
Tibor Karaszi
3
Quem votou negativamente nisso? É uma pergunta muito detalhada e bem pesquisada, mesmo sem os planos de consulta! +1 de mim!
Vérace 29/07/19
4
Como você chegou à conclusão de que os planos de consulta são "idênticos"? Você quer dizer que eles têm a mesma forma? Poste-os em algum lugar para que possamos comparar também. Apenas nos dizer que eles são idênticos não significa que eles são idênticos.
Aaron Bertrand
3
Adicionar os planos de execução brutais usando PasteThePlan pode nos dar uma idéia do que a consulta estava aguardando.
Randi Vertongen

Respostas:

2

Dê uma olhada no wait_stats e ele mostrará quais são os maiores gargalos no seu servidor SQL.

Recentemente, experimentei um problema em que um aplicativo externo era intermitentemente lento. A execução de procedimentos armazenados no próprio servidor sempre foi rápida.

O monitoramento de desempenho não mostrou nada para se preocupar com os caches SQL ou o uso de RAM e E / S no servidor.

O que ajudou a restringir a investigação foi consultar as estatísticas de espera coletadas pelo SQL no sys.dm_os_wait_stats

O excelente script no site SQLSkills mostrará os que você está enfrentando mais. Você pode restringir sua pesquisa para identificar as causas.

Depois de saber quais são as grandes questões, este script ajudará a restringir que sessão / banco de dados está enfrentando as esperas:

SELECT OSW.session_id,
       OSW.wait_duration_ms,
       OSW.wait_type,
       DB_NAME(EXR.database_id) AS DatabaseName
FROM sys.dm_os_waiting_tasks OSW
INNER JOIN sys.dm_exec_sessions EXS ON OSW.session_id = EXS.session_id
INNER JOIN sys.dm_exec_requests EXR ON EXR.session_id = OSW.session_id
OPTION(Recompile);

A consulta acima e mais detalhes são do site MSSQLTips .

O sp_BlitzFirstscript do site de Brent Ozar também mostrará o que está causando lentidão.

KevH
fonte