Procedimiento almacenado lento cuando se llama desde la web, rápido desde Management Studio

97

Tengo un procedimiento almacenado que se agota cada vez que se llama desde la aplicación web.

Encendí el Sql Profiler y rastreé las llamadas que se agotaron y finalmente descubrí estas cosas:

  1. Cuando se ejecutan las declaraciones desde dentro de MS SQL Management Studio, con los mismos argumentos (de hecho, copié la llamada al procedimiento desde el rastreo del perfil sql y la ejecuté): finaliza en un promedio de 5 ~ 6 segundos.
  2. Pero cuando se llama desde la aplicación web, tarda más de 30 segundos (en seguimiento), por lo que mi página web se agota para entonces.

Aparte del hecho de que mi aplicación web tiene su propio usuario, todo es igual (misma base de datos, conexión, servidor, etc.) También intenté ejecutar la consulta directamente en el estudio con el usuario de la aplicación web y no se necesitan más de 6 segundo.

¿Cómo averiguo lo que está pasando?

Supongo que no tiene nada que ver con el hecho de que usamos BLL> capas DAL o adaptadores de tabla, ya que el rastro muestra claramente que el retraso está en el procedimiento real. Eso es todo lo que puedo pensar.

EDITAR Descubrí en este enlace que ADO.NET se establece ARITHABORTen verdadero, lo cual es bueno la mayor parte del tiempo, pero en algún momento esto sucede, y la solución with recompilealternativa sugerida es agregar una opción al proceso almacenado. En mi caso, no funciona, pero sospecho que es algo muy similar a esto. ¿Alguien sabe qué más hace ADO.NET o dónde puedo encontrar la especificación?

lo digo en serio
fuente
1
¿Esto podría estar relacionado con la cantidad de datos que se devuelven?
Barry Kaye
@Barry: No, como ejecuto el mismo procedimiento (también copiado del seguimiento, es decir, los mismos parámetros) en Management Studio, se ejecuta en 6 segundos.
iamserious
@Jayantha: El punto NO es que el sp sea lento, pero ALGO entre ado.net y sql sí lo es. No veo cómo el sp haría alguna diferencia.
iamserious
2
¿El SP devuelve muchos datos, por ejemplo, columnas de imagen / texto / varchar (max)? La cantidad de datos a consumir en el cliente sería enorme, lo que llevará mucho tiempo. SSMS corta estos conjuntos de resultados de una manera más eficiente.
Tim Schmelter
1
Posible duplicado de stackoverflow.com/questions/2248112/…
Aaron Bertrand

Respuestas:

79

Me ha surgido un problema similar en el pasado, así que estoy ansioso por ver una solución a esta pregunta. El comentario de Aaron Bertrand sobre el OP provocó que la consulta se agotara cuando se ejecutaba desde la web, pero superrápido cuando se ejecutaba desde SSMS , y aunque la pregunta no es un duplicado, la respuesta puede muy bien aplicarse a su situación.

En esencia, parece que SQL Server puede tener un plan de ejecución en caché dañado. Está logrando un plan incorrecto con su servidor web, pero SSMS aterriza en un plan diferente ya que hay una configuración diferente en el indicador ARITHABORT (que de otro modo no tendría ningún impacto en su consulta particular / proceso almacenado).

Consulte ADO.NET llamando al procedimiento almacenado T-SQL que provoca una SqlTimeoutException para ver otro ejemplo, con una explicación y resolución más completas.

StriplingGuerrero
fuente
Estos son algunos clientes potenciales interesantes, leeré más sobre ellos y los responderé en unos pocos ... gracias.
iamserious
44
Hola, ¡Limpiar la caché DBCC DROPCLEANBUFFERSe DBCC FREEPROCCACHEhizo el truco! Supongo que el plan de ejecución estaba dañado de alguna manera o no se actualizó. Dudo que sea una solución permanente, si pudiera corromperse una vez, podría volver a hacerlo. Así que sigo investigando las causas plausibles. Dado que la aplicación web ha vuelto a funcionar, no necesito sentir la presión. Muchas gracias.
iamserious
2
@iamserious - ¡lo lograste gracias! Después de alterar mi procedimiento almacenado, estaba teniendo el problema del tiempo de espera. Luego ejecuté los dos comandos DBCC que mencionaste anteriormente y resolvió el problema.
Induster
5
@Mangist: Debido a que este es un tema complicado, no hay una solución milagrosa, pero puede intentar usar OPTIMIZE FORo OPTION(Recompile)consultar sugerencias sobre las consultas que están causando problemas. Este artículo analiza el problema en profundidad. Si Entity Framework está generando sus consultas, esta publicación parece proporcionar una forma de agregar una sugerencia de consulta a sus consultas.
StriplingWarrior
8
En lugar de ejecutar DBCC DROPCLEANBUFFERS y DBCC FREEPROCCACHE, que borrarán TODOS los planes de ejecución, puede eliminar y volver a crear el procedimiento almacenado del problema.
jnoreiga
50

También experimenté que las consultas se ejecutaban lentamente desde la web y rápidamente en SSMS y finalmente descubrí que el problema era algo llamado detección de parámetros.

La solución para mí fue cambiar todos los parámetros que se usan en el sproc a variables locales.

p.ej. cambio:

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

a:

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

Parece extraño, pero solucionó mi problema.

Zane
fuente
3
Vaya, tuve el mismo problema y no creí que esto funcionaría, pero lo hizo.
Lac Ho
1
Zane, ¿sabes si esto solucionará el problema de forma permanente o puede volver? ¡Gracias!
Lac Ho
1
Desde que hice este cambio, no he tenido ningún problema con la velocidad del procedimiento almacenado.
Zane
1
¡Vaya, también solucionó mi problema! Esto TIENE QUE SER un error en el servidor SQL. ¿Por qué obligar a los escritores de sql a pasar por este tonto aro cuando MSSQL podría convertir internamente a local bajo el capó, para obtener ENORMES ganancias de rendimiento?
HerrimanCoder
3
¿Podría ser que la alteración del proceso provoque la creación de un nuevo plan de ejecución, por lo que la solución no es realmente copiar la variable de entrada a una variable local sino simplemente forzar la generación de un nuevo plan de ejecución (como se explica en la solución aceptada)?
Garland Pope
10

No al spam, pero como una solución útil para otros, nuestro sistema vio un alto grado de tiempos de espera.

Intenté configurar el procedimiento almacenado para que se recompile usando sp_recompiley esto resolvió el problema para el único SP.

En última instancia, hubo una mayor cantidad de SP que agotaron el tiempo de espera, muchos de los cuales nunca lo habían hecho antes, mediante el uso, DBCC DROPCLEANBUFFERSy DBBC FREEPROCCACHEla tasa de incidentes de tiempos de espera se ha desplomado significativamente; todavía hay casos aislados, algunos en los que sospecho que la regeneración del plan está tomando por un tiempo, y en algunos casos en los que los PE están realmente por debajo del rendimiento y necesitan una reevaluación.

Clint
fuente
1
Gracias por esto. Marcar el procedimiento para la recompilación con el comando sp_recompile funcionó para mí cuando DBCC DROPCLEANBUFFERSy DBCC FREEPROCCACHEno hizo ninguna diferencia.
Steve
4

¿Podría ser que algunas otras llamadas a la base de datos realizadas antes de que la aplicación web llame al SP mantiene una transacción abierta? Esa podría ser una razón para que este SP espere cuando lo llame la aplicación web. Digo aislar la llamada en la aplicación web (ponerla en una página nueva) para asegurarme de que alguna acción previa en la aplicación web esté causando este problema.

Tundey
fuente
1
Hola @Tundey, aislé la llamada y todavía está demorando aproximadamente 30 segundos y se agota el tiempo de espera. así que tiene que haber algo entre la comunicación, ¿supongo?
iamserious
1

Simplemente recompilar el procedimiento almacenado (función de tabla en mi caso) funcionó para mí

Guy Biber
fuente
1

como @Zane dijo que podría deberse a la detección de parámetros. Experimenté el mismo comportamiento y eché un vistazo al plan de ejecución del procedimiento y todas las declaraciones del sp en una fila (copié todas las declaraciones del procedimiento, declaré los parámetros como variables y asigné los mismos valores para la variable como los parámetros tenían). Sin embargo, el plan de ejecución se veía completamente diferente. La ejecución de sp tomó 3-4 segundos y las declaraciones en una fila con exactamente los mismos valores se devolvieron instantáneamente.

plan de ejecución

Después de buscar en Google, encontré una lectura interesante sobre ese comportamiento: ¿ Lento en la aplicación, Rápido en SSMS?

Al compilar el procedimiento, SQL Server no sabe que el valor de @fromdate cambia, pero compila el procedimiento bajo el supuesto de que @fromdate tiene el valor NULL. Dado que todas las comparaciones con NULL dan como resultado UNKNOWN, la consulta no puede devolver ninguna fila, si @fromdate todavía tiene este valor en tiempo de ejecución. Si SQL Server tomara el valor de entrada como la verdad final, podría construir un plan con solo un escaneo constante que no acceda a la tabla en absoluto (ejecute la consulta SELECT * FROM Orders WHERE OrderDate> NULL para ver un ejemplo de esto) . Pero SQL Server debe generar un plan que devuelva el resultado correcto sin importar qué valor tenga @fromdate en tiempo de ejecución. Por otro lado, no existe la obligación de construir un plan que sea el mejor para todos los valores. Por lo tanto, dado que se supone que no se devolverán filas, SQL Server se conforma con la búsqueda de índice.

El problema era que tenía parámetros que podían dejarse como nulos y si se pasaban como nulos, se inicializarían con un valor predeterminado.

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

Después de que lo cambié a

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

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

funcionó como un encanto de nuevo.

TomPez
fuente