Procédure stockée lente lorsqu'elle est appelée depuis le Web, rapide depuis Management Studio

97

J'ai stocké une procédure qui expire incroyablement à chaque fois qu'elle est appelée à partir de l'application Web.

J'ai lancé le Sql Profiler et tracé les appels qui expirent et finalement découvert ces choses:

  1. Lors de l'exécution des instructions à partir du MS SQL Management Studio, avec les mêmes arguments (en fait, j'ai copié l'appel de procédure à partir de la trace de profil SQL et l'ai exécuté): Il se termine en 5 ~ 6 secondes en moyenne.
  2. Mais lorsqu'il est appelé à partir d'une application Web, cela prend plus de 30 secondes (dans la trace), donc ma page Web expire réellement d'ici là.

Outre le fait que mon application Web a son propre utilisateur, tout est identique (même base de données, connexion, serveur, etc.) J'ai également essayé d'exécuter la requête directement dans le studio avec l'utilisateur de l'application Web et cela ne prend pas plus de 6 seconde.

Comment savoir ce qui se passe?

Je suppose que cela n'a rien à voir avec le fait que nous utilisons des couches BLL> DAL ou des adaptateurs de table car la trace montre clairement que le retard est dans la procédure réelle. C'est tout ce que je peux penser.

EDIT J'ai découvert dans ce lien qu'ADO.NET définit ARITHABORTsur true - ce qui est bon pour la plupart du temps, mais cela se produit parfois, et la with recompilesolution de contournement suggérée consiste à ajouter une option au processus stocké. Dans mon cas, cela ne fonctionne pas mais je soupçonne que c'est quelque chose de très similaire à cela. Tout le monde sait ce que ADO.NET fait d'autre ou où je peux trouver les spécifications?

je suis sérieux
la source
1
Cela pourrait être lié à la quantité de données renvoyée?
Barry Kaye
@Barry: Non, comme j'exécute la même procédure (copiée à partir de trace aussi - ce qui signifie les mêmes paramètres) dans le studio de gestion, elle s'exécute dans les 6 secondes.
iamserious
@Jayantha: Le fait n'est PAS que le SP soit lent, mais QUELQUE CHOSE entre ado.net et sql l'est. Je ne vois pas comment le SP ferait une différence.
iamserious
2
Le SP renvoie-t-il beaucoup de données, par exemple des colonnes image / text / varchar (max)? La quantité de données à consommer sur le client serait énorme, ce qui prendra beaucoup de temps. SSMS coupe ces ensembles de résultats de manière plus efficace.
Tim Schmelter
1
Copie possible de stackoverflow.com/questions/2248112/…
Aaron Bertrand

Réponses:

79

J'ai eu un problème similaire dans le passé, donc je suis impatient de voir une solution à cette question. Le commentaire d'Aaron Bertrand sur l'OP a conduit à l' expiration de la requête lorsqu'elle est exécutée à partir du Web, mais ultra-rapide lorsqu'elle est exécutée à partir de SSMS , et bien que la question ne soit pas un double, la réponse peut très bien s'appliquer à votre situation.

En substance, il semble que SQL Server puisse avoir un plan d'exécution mis en cache corrompu. Vous rencontrez le mauvais plan avec votre serveur Web, mais SSMS atterrit sur un plan différent car il existe un paramètre différent sur l'indicateur ARITHABORT (qui autrement n'aurait aucun impact sur votre requête / procédure stockée particulière).

Voir ADO.NET appeler une procédure stockée T-SQL provoque une exception SqlTimeoutException pour un autre exemple, avec une explication et une résolution plus complètes.

StriplingGuerrier
la source
Voici quelques pistes intéressantes, vous en lirez plus à leur sujet et reviendrons dans quelques-uns .. merci.
iamserious
44
Salut, Effacer le cache DBCC DROPCLEANBUFFERSet a DBCC FREEPROCCACHEfait l'affaire! Je suppose que le plan d'exécution était en quelque sorte corrompu ou non mis à jour. Je doute que ce soit une solution permanente, s'il pouvait être corrompu une fois, il pourrait le faire à nouveau. Je suis donc toujours à la recherche des causes plausibles. Depuis que l'application Web est de nouveau au travail, je n'ai pas besoin de ressentir la pression. Merci beaucoup.
iamserious le
2
@iamserious - vous avez réussi grâce! Après avoir modifié ma procédure stockée, j'avais un problème de délai d'expiration. Ensuite, j'ai exécuté les deux commandes DBCC que vous mentionnez ci-dessus et cela a résolu le problème.
Induster
5
@Mangist: Comme il s'agit d'un problème compliqué, il n'y a pas de solution miracle, mais vous pouvez essayer d'utiliser OPTIMIZE FORou d' OPTION(Recompile)interroger des indices sur les requêtes qui posent des problèmes. Cet article décrit le problème en profondeur. Si Entity Framework génère vos requêtes, cet article semble fournir un moyen d'ajouter un indice de requête à vos requêtes.
StriplingWarrior
8
Au lieu d'exécuter DBCC DROPCLEANBUFFERS et DBCC FREEPROCCACHE qui effacera TOUS les plans d'exécution, vous pouvez supprimer et recréer la procédure stockée problématique.
jnoreiga
50

J'ai également constaté que les requêtes s'exécutaient lentement à partir du Web et rapidement dans SSMS et j'ai finalement découvert que le problème était quelque chose appelé le reniflage de paramètres.

Le correctif pour moi était de changer tous les paramètres utilisés dans le sproc en variables locales.

par exemple. changement:

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

à:

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

Cela semble étrange, mais cela a résolu mon problème.

Zane
la source
3
Wow, j'ai eu le même problème et je ne pensais pas que cela fonctionnerait, mais cela a fonctionné.
Lac Ho
1
Zane, savez-vous si cela résoudra définitivement le problème ou si cela peut revenir? Je vous remercie!
Lac Ho
1
Depuis cette modification, je n'ai eu aucun problème avec la vitesse de la procédure stockée.
Zane
1
Wow, cela a aussi résolu mon problème! Cela DOIT ÊTRE un bogue dans le serveur SQL. Pourquoi forcer les écrivains SQL à sauter à travers ce cerceau stupide alors que MSSQL pourrait convertir en interne en local sous le capot, pour des gains de performances ÉNORMES?
HerrimanCoder
3
Se pourrait-il que la modification du processus entraîne la création d'un nouveau plan d'exécution, de sorte que la solution ne copie pas vraiment la variable d'entrée dans une variable locale mais oblige simplement la génération d'un nouveau plan d'exécution (comme expliqué dans la solution acceptée)?
Garland Pope
10

Pas pour le spam, mais comme solution, espérons-le, utile pour les autres, notre système a connu un degré élevé de délais d'attente.

J'ai essayé de définir la procédure stockée à recompiler à l'aide de sp_recompileet cela a résolu le problème pour le seul SP.

En fin de compte, il y avait un plus grand nombre de SP qui expiraient, dont beaucoup ne l'avaient jamais fait auparavant, en utilisant DBCC DROPCLEANBUFFERSet DBBC FREEPROCCACHEle taux d'incident des délais d'expiration a considérablement chuté - il y a encore des événements isolés, certains où je soupçonne que la régénération du plan prend un certain temps, et d'autres où les SP sont vraiment sous-performants et doivent être réévalués.

Clint
la source
1
Merci pour cela. Marquer la procédure de recompilation à l'aide de la commande sp_recompile a fonctionné pour moi lorsque DBCC DROPCLEANBUFFERSet DBCC FREEPROCCACHEn'a fait aucune différence.
Steve
4

Se pourrait-il que certains autres appels de base de données effectués avant que l'application Web appelle le SP maintiennent une transaction ouverte? Cela pourrait être une raison pour que ce SP attende lorsqu'il est appelé par l'application Web. Je dis isoler l'appel dans l'application Web (placez-le sur une nouvelle page) pour vous assurer qu'une action préalable dans l'application Web est à l'origine de ce problème.

Tundey
la source
1
Salut @Tundey, j'ai isolé l'appel et cela prend encore environ 30 secondes et expire. donc il doit y avoir quelque chose entre la communication, je suppose?
iamserious
1

Recompiler simplement la procédure stockée (fonction de table dans mon cas) a fonctionné pour moi

Guy Biber
la source
1

comme @Zane l'a dit, cela pourrait être dû au reniflage de paramètres. J'ai vécu le même comportement et j'ai jeté un coup d'œil au plan d'exécution de la procédure et à toutes les instructions du sp dans une ligne (copié toutes les instructions de la procédure, déclaré les paramètres en tant que variables et attribué les mêmes valeurs pour la variable que les paramètres avaient). Cependant, le plan d'exécution était complètement différent. L'exécution de sp a pris 3-4 secondes et les instructions dans une ligne avec exactement les mêmes valeurs ont été instantanément renvoyées.

plan d'exécution

Après quelques recherches sur Google, j'ai trouvé une lecture intéressante sur ce comportement: lent dans l'application, rapide dans SSMS?

Lors de la compilation de la procédure, SQL Server ne sait pas que la valeur de @fromdate change, mais compile la procédure en supposant que @fromdate a la valeur NULL. Étant donné que toutes les comparaisons avec NULL génèrent INCONNU, la requête ne peut pas renvoyer du tout de ligne, si @fromdate a toujours cette valeur au moment de l'exécution. Si SQL Server prendrait la valeur d'entrée comme vérité finale, il pourrait construire un plan avec uniquement une analyse constante qui n'accède pas du tout à la table (exécutez la requête SELECT * FROM Orders WHERE OrderDate> NULL pour voir un exemple de ceci) . Mais SQL Server doit générer un plan qui renvoie le résultat correct, quelle que soit la valeur de @fromdate au moment de l'exécution. D'autre part, il n'y a aucune obligation de construire un plan qui soit le meilleur pour toutes les valeurs. Ainsi, puisque l'hypothèse est qu'aucune ligne ne sera renvoyée, SQL Server installe la recherche d'index.

Le problème était que j'avais des paramètres qui pouvaient être laissés nuls et s'ils étaient passés comme null, ils seraient initialisés avec une valeur par défaut.

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

Après l'avoir changé en

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

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

cela fonctionna à nouveau comme un charme.

TomPez
la source