Procedimento armazenado lento quando chamado da web, rápido do Management Studio


97

Eu armazenei um procedimento que atinge o tempo limite insanamente toda vez que é chamado do aplicativo da web.

Eu iniciei o Sql Profiler e rastreei as chamadas que expiraram e finalmente descobri estas coisas:

  1. Ao executar as instruções de dentro do MS SQL Management Studio, com os mesmos argumentos (na verdade, copiei a chamada de procedimento do rastreamento do perfil sql e executei): Termina em 5 ~ 6 segundos em média.
  2. Mas, quando chamado de um aplicativo da web, leva mais de 30 segundos (em rastreamento), então minha página da web atinge o tempo limite até lá.

Além do fato de que meu aplicativo web tem seu próprio usuário, tudo é igual (mesmo banco de dados, conexão, servidor etc), também tentei executar a consulta diretamente no estúdio com o usuário do aplicativo web e não leva mais de 6 seg.

Como faço para descobrir o que está acontecendo?

Estou assumindo que não tem nada a ver com o fato de usarmos camadas BLL> DAL ou adaptadores de tabela, pois o rastreamento mostra claramente que o atraso está no procedimento real. Isso é tudo em que consigo pensar.

EDIT Eu descobri neste link que o ADO.NET define ARITHABORTcomo verdadeiro - o que é bom na maioria das vezes, mas às vezes isso acontece, e a solução sugerida é adicionar uma with recompileopção ao procedimento armazenado. No meu caso, não está funcionando, mas suspeito que seja algo muito semelhante a isso. Alguém sabe o que mais o ADO.NET faz ou onde posso encontrar a especificação?


1
Isso pode estar relacionado a quantos dados estão sendo retornados?
Barry Kaye

@Barry: Não, como eu executo o mesmo procedimento (copiado do rastreamento também - significando os mesmos parâmetros) no estúdio de gerenciamento, ele é executado em 6 segundos.
iamserious

@Jayantha: A questão NÃO é que o sp seja lento, mas ALGUMA COISA entre ado.net e sql é. Não vejo como o sp faria alguma diferença.
iamserious

2
O SP retorna muitos dados, por exemplo colunas de imagem / texto / varchar (máx)? A quantidade de dados a consumir no cliente seria enorme, o que levaria muito tempo. O SSMS corta esses conjuntos de resultados de uma forma mais eficiente.
Tim Schmelter

Respostas:


79

Já tive um problema semelhante no passado, por isso estou ansioso para ver uma solução para essa questão. O comentário de Aaron Bertrand sobre o OP levou ao tempo limite da Consulta quando executado da web, mas muito rápido quando executado do SSMS , e embora a pergunta não seja uma duplicata, a resposta pode muito bem se aplicar à sua situação.

Em essência, parece que o SQL Server pode ter um plano de execução em cache corrompido. Você está atingindo o plano ruim com o seu servidor web, mas o SSMS cai em um plano diferente, pois há uma configuração diferente no sinalizador ARITHABORT (que, de outra forma, não teria impacto em sua consulta / proc armazenado em particular).

Consulte ADO.NET chamando o procedimento armazenado T-SQL causa uma SqlTimeoutException para outro exemplo, com uma explicação e resolução mais completas.


Estas são algumas pistas interessantes, lerei mais sobre elas e voltarei em alguns ... obrigado.
iamserious

44
Olá, Limpei o cache DBCC DROPCLEANBUFFERSe DBCC FREEPROCCACHEresolvi! Estou supondo que o plano de execução foi de alguma forma corrompido ou não atualizado. Tenho dúvidas de que seja uma solução permanente, se pudesse ser corrompido uma vez, poderia corromper novamente. Portanto, ainda estou procurando as causas plausíveis. Como o aplicativo da web voltou a funcionar, não preciso sentir a pressão. Muito obrigado.
iamserious

2
@iamserious - você acertou em cheio, obrigado! Depois de alterar meu procedimento armazenado, eu estava tendo o problema de tempo limite. Em seguida, executei os dois comandos DBCC que você mencionou acima e resolveu o problema.
Induster

5
@Mangist: Como esse é um problema complicado, não existe solução mágica, mas você pode tentar usar OPTIMIZE FORou OPTION(Recompile)consultar dicas nas consultas que estão causando problemas. Este artigo discute o problema em detalhes. Se o Entity Framework estiver gerando suas consultas, esta postagem parece fornecer uma maneira de adicionar uma dica de consulta às suas consultas.
StriplingWarrior

8
Em vez de executar DBCC DROPCLEANBUFFERS e DBCC FREEPROCCACHE, que limpará TODOS os planos de execução, você pode descartar e recriar o procedimento armazenado do problema.
jnoreiga de

50

Também percebi que as consultas estavam sendo executadas lentamente na web e rápidas no SSMS e, finalmente, descobri que o problema era algo chamado de detecção de parâmetro.

A correção para mim foi alterar todos os parâmetros usados ​​no sproc para variáveis ​​locais.

por exemplo. mudança:

ALTER PROCEDURE [dbo].[sproc] 
    @param1 int,
AS
SELECT * FROM Table WHERE ID = @param1 

para:

ALTER PROCEDURE [dbo].[sproc] 
    @param1 int,
AS
DECLARE @param1a int
SET @param1a = @param1
SELECT * FROM Table WHERE ID = @param1a

Parece estranho, mas resolveu meu problema.


3
Uau, eu tive o mesmo problema e não acreditei que isso iria funcionar, mas funcionou.
Lac Ho

1
Zane, você sabe se isso vai resolver o problema permanentemente ou pode voltar? Obrigado!
Lac Ho

1
Desde que fiz essa alteração, não tive problemas com a velocidade do procedimento armazenado.
Zane

1
Uau, também corrigiu o meu problema! Isso DEVE SER um bug no servidor sql. Por que forçar os escritores de sql a pularem esse obstáculo bobo quando o MSSQL poderia ser convertido internamente para local nos bastidores, para ganhos de desempenho ENORMES?
HerrimanCoder

3
Será que alterar o proc faz com que um novo plano de execução seja criado, então a solução não é realmente copiar a variável de entrada para uma variável local, mas apenas forçar a geração de um novo plano de execução (conforme explicado na solução aceita)?
Garland Pope,

10

Não para spam, mas como uma solução útil para outras pessoas, nosso sistema apresentou um alto grau de tempo limite.

Tentei configurar o procedimento armazenado para ser recompilado usando sp_recompilee isso resolveu o problema para um SP.

No final das contas, havia um grande número de SPs que estavam expirando, muitos dos quais nunca tinham feito isso antes, usando DBCC DROPCLEANBUFFERSe DBBC FREEPROCCACHEa taxa de incidentes de tempos limite caiu significativamente - ainda há ocorrências isoladas, algumas onde eu suspeito que a regeneração do plano está levando um tempo, e alguns onde os SPs estão genuinamente abaixo do desempenho e precisam ser reavaliados.


1
Obrigado por isso. Marcar o procedimento de recompilação usando o comando sp_recompile funcionou para mim quando o DBCC DROPCLEANBUFFERSe DBCC FREEPROCCACHEnão fez diferença.
Steve

4

Será que alguma outra chamada de banco de dados feita antes de o aplicativo da web chamar o SP está mantendo uma transação aberta? Esse pode ser um motivo para este SP esperar quando chamado pelo aplicativo da web. Eu digo isolar a chamada no aplicativo da web (colocá-la em uma nova página) para garantir que alguma ação anterior no aplicativo da web esteja causando esse problema.


1
Olá @Tundey, isolei a chamada e ela ainda está levando cerca de 30 segundos e o tempo limite se esgotou. então tem que ser algo entre a comunicação, não é?
iamserious

1

Simplesmente recompilar o procedimento armazenado (função de tabela no meu caso) funcionou para mim


1

como @Zane disse que pode ser devido à detecção de parâmetros. Eu experimentei o mesmo comportamento e dei uma olhada no plano de execução do procedimento e todas as declarações do sp em uma linha (copiei todas as declarações do procedimento, declarei os parâmetros como variáveis ​​e atribuí os mesmos valores para a variável como os parâmetros tinham). No entanto, o plano de execução parecia completamente diferente. A execução do sp levou de 3 a 4 segundos e as instruções em uma linha com exatamente os mesmos valores foram retornadas instantaneamente.

plano de execução

Depois de pesquisar no Google, achei uma leitura interessante sobre esse comportamento: lento no aplicativo, rápido no SSMS?

Ao compilar o procedimento, o SQL Server não sabe que o valor de @fromdate muda, mas compila o procedimento assumindo que @fromdate tem o valor NULL. Como todas as comparações com NULL resultam em UNKNOWN, a consulta não pode retornar nenhuma linha, se @fromdate ainda tiver este valor em tempo de execução. Se o SQL Server considerasse o valor de entrada como a verdade final, ele poderia construir um plano com apenas uma Varredura Constante que não acessa a tabela de forma alguma (execute a consulta SELECT * FROM Pedidos WHERE DataPedido> NULL para ver um exemplo disso) . Mas o SQL Server deve gerar um plano que retorne o resultado correto, independentemente do valor @fromdate em tempo de execução. Por outro lado, não há obrigação de construir um plano que seja o melhor para todos os valores. Portanto, como a suposição é de que nenhuma linha será retornada, o SQL Server define a busca de índice.

O problema é que eu tinha parâmetros que podiam ser deixados nulos e, se fossem passados ​​como nulos, seriam inicializados com um valor padrão.

create procedure dbo.procedure
    @dateTo datetime = null
begin
    if (@dateTo is null)
    begin
        select @dateTo  = GETUTCDATE()
    end

    select foo
    from dbo.table
    where createdDate < @dateTo
end

Depois que mudei para

create procedure dbo.procedure
    @dateTo datetime = null
begin
    declare @to datetime = coalesce(@dateTo, getutcdate())

    select foo
    from dbo.table
    where createdDate < @to
end 

funcionou como um encanto novamente.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.