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:
E para esse balde "rápido" , é muito menos que 1s. Na verdade, é cerca de 90 ms:
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 :
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:
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:
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:
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:
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:
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:
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.