Una consulta se ejecuta rápido:
DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
costo de subárbol: 0.502
Pero poner el mismo SQL en un procedimiento almacenado se ejecuta lentamente y con un plan de ejecución totalmente diferente
CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
EXECUTE ViewOpener @SessionGUID
Costo del subárbol: 19.2
He corrido
sp_recompile ViewOpener
Y todavía funciona igual (mal), y también he cambiado el procedimiento almacenado a
CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *, 'recompile please'
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
Y de vuelta, intentando engañarlo para que vuelva a compilar.
Dejé caer y recreé el procedimiento almacenado para que genere un nuevo plan.
He intentado forzar recompilaciones y evitar el rastreo de parámetros , utilizando una variable señuelo:
CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
DECLARE @SessionGUIDbitch uniqueidentifier
SET @SessionGUIDbitch = @SessionGUID
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUIDbitch
ORDER BY CurrencyTypeOrder, Rank
También he intentado definir el procedimiento almacenado WITH RECOMPILE
:
CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier
WITH RECOMPILE
AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
Para que su plan nunca se almacene en caché, y he intentado forzar una recompilación en la ejecución:
EXECUTE ViewOpener @SessionGUID WITH RECOMPILE
Lo cual no ayudó.
Intenté convertir el procedimiento a SQL dinámico:
CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier
WITH RECOMPILE AS
DECLARE @SQLString NVARCHAR(500)
SET @SQLString = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank'
EXECUTE sp_executesql @SQLString,
N'@SessionGUID uniqueidentifier',
@SessionGUID
Lo cual no ayudó.
La entidad " Report_Opener
" es una vista, que no está indexada. La vista solo hace referencia a tablas subyacentes. Ninguna tabla contiene columnas calculadas, indexadas o no.
Por el simple hecho de que traté de crear la vista con
SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON
Eso no lo solucionó.
Cómo es que
- la consulta es rápida
- mover la consulta a una vista y seleccionar desde la vista es rápido
- seleccionar desde la vista desde un procedimiento almacenado es 40 veces más lento?
Traté de mover la definición de la vista directamente al procedimiento almacenado (violando 3 reglas comerciales y rompiendo una encapsulación importante), y eso hace que sea solo 6 veces más lento.
¿Por qué la versión del procedimiento almacenado es tan lenta? ¿Qué puede explicar el SQL Server que ejecuta SQL ad-hoc más rápido que un tipo diferente de SQL ad-hoc?
Realmente prefiero no
- incrustar el SQL en el código
cambiar el código en absoluto
Microsoft SQL Server 2000 - 8.00.2050 (Intel X86) Mar 7 2008 21:29:56 Copyright (c) 1988-2003 Microsoft Corporation Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
Pero, ¿qué puede explicar que SQL Server no pueda ejecutarse tan rápido como SQL Sever ejecuta una consulta, si no es que detecta parámetros?
Mi próximo intento será hacer que StoredProcedureA
call StoredProcedureB
call StoredProcedureC
call StoredProcedureD
para consultar la vista.
Y en su defecto, haga que el procedimiento almacenado llame a un procedimiento almacenado, llame a una UDF, llame a una UDF, llame a un procedimiento almacenado, llame a una UDF para consultar la vista.
En resumen, lo siguiente corre rápido desde QA, pero lento cuando se coloca en un procedimiento almacenado:
El original:
--Runs fine outside of a stored procedure
SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
sp_executesql
:
--Runs fine outside of a stored procedure
DECLARE @SQLString NVARCHAR(500)
SET @SQLString = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank'
EXECUTE sp_executesql @SQLString,
N'@SessionGUID uniqueidentifier',
@SessionGUID
EXEC(@sql)
:
--Runs fine outside of a stored procedure
DECLARE @sql NVARCHAR(500)
SET @sql = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = '''+CAST(@SessionGUID AS varchar(50))+'''
ORDER BY CurrencyTypeOrder, Rank'
EXEC(@sql)
Planes de ejecucion
El buen plan
|--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC))
|--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[CurrencyType]
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID]))
|--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currencies].
| |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH)
| |--Nested Loops(Left Outer Join)
| | |--Bookmark Lookup(BOOKMARK:([Bmk1016]), OBJECT:([GrobManagementSystemLive].[dbo].[Windows]))
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([Openers].[WindowGUID]))
| | | |--Bookmark Lookup(BOOKMARK:([Bmk1014]), OBJECT:([GrobManagementSystemLive].[dbo].[Openers]))
| | | | |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_SessionGUID]), SEEK:([Openers].[SessionGUID]=[@SessionGUID]) ORDERED FORWARD)
| | | |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows]), SEEK:([Windows].[WindowGUID]=[Openers].[WindowGUID]) ORDERED FORWARD)
| | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
| |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Currenc
|--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]))
|--Stream Aggregate(DEFINE:([Expr1006]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='ctCanadianCoin') OR [
|--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH)
|--Nested Loops(Inner Join)
| |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
| |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
|--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD)
El mal plan
|--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC))
|--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[Currency
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID]))
|--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currenc
| |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH)
| |--Filter(WHERE:([Openers].[SessionGUID]=[@SessionGUID]))
| | |--Concatenation
| | |--Nested Loops(Left Outer Join)
| | | |--Table Spool
| | | | |--Hash Match(Inner Join, HASH:([Windows].[WindowGUID])=([Openers].[WindowGUID]), RESIDUAL:([Windows].[WindowGUID]=[Openers].[WindowGUID]))
| | | | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows_CageGUID]))
| | | | |--Table Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Openers]))
| | | |--Table Spool
| | | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
| | |--Compute Scalar(DEFINE:([Openers].[OpenerGUID]=NULL, [Openers].[SessionGUID]=NULL, [Windows].[UseChipDenominations]=NULL))
| | |--Nested Loops(Left Anti Semi Join)
| | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
| | |--Row Count Spool
| | |--Table Spool
| |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Cu
|--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]))
|--Stream Aggregate(DEFINE:([Expr1006]=SUM([partialagg1034]), [Expr1007]=SUM([partialagg1035]), [Expr1008]=SUM([partialagg1036]), [Expr1009]=SUM([partialagg1037]), [Expr1010]=SUM([partialagg1038]), [Expr1011]=SUM([partialagg1039]
|--Nested Loops(Inner Join)
|--Stream Aggregate(DEFINE:([partialagg1034]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='
| |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH)
| |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
| |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD)
|--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
El malo está ansioso por enrollar 6 millones de filas; el otro no lo es.
Nota: Esta no es una pregunta sobre cómo ajustar una consulta. Tengo una consulta que se ejecuta a la velocidad del rayo. Solo quiero que SQL Server se ejecute rápidamente desde un procedimiento almacenado.
fuente
Respuestas:
Tuve el mismo problema que el póster original, pero la respuesta citada no me resolvió el problema. La consulta todavía se ejecutó muy lentamente desde un procedimiento almacenado.
Encontré otra respuesta aquí "Parameter Sniffing" , gracias Omnibuzz. Se reduce al uso de "Variables locales" en sus consultas de procedimientos almacenados, pero lea el original para una mejor comprensión, es una excelente redacción. p.ej
Camino lento:
Manera rápida:
Espero que esto ayude a alguien más, hacerlo redujo mi tiempo de ejecución de más de 5 minutos a unos 6-7 segundos.
fuente
WITH RECOMPILE
no marcó la diferencia para mí, solo los parámetros locales.Encontré el problema, aquí está el script de las versiones lenta y rápida del procedimiento almacenado:
dbo.ViewOpener__RenamedForCruachan__Slow.PRC
dbo.ViewOpener__RenamedForCruachan__Fast.PRC
Si no viste la diferencia, no te culpo. La diferencia no está en el procedimiento almacenado en absoluto. La diferencia que convierte una consulta rápida de 0.5 en una consulta que genera 6 millones de filas:
Lento:
SET ANSI_NULLS OFF
Rápido:
SET ANSI_NULLS ON
Esta respuesta también podría tener sentido, ya que la vista tiene una cláusula de unión que dice:
Entonces hay algunos
NULL
s involucrados.La explicación se demuestra aún más al volver al Analizador de consultas y ejecutar
.
.
Y la consulta es lenta.
Entonces, el problema no es porque la consulta se ejecuta desde un procedimiento almacenado. El problema es que la opción predeterminada de conexión de Enterprise Manager es
ANSI_NULLS off
, en lugar deANSI_NULLS on
, cuál es el valor predeterminado de QA.Microsoft reconoce este hecho en KB296769 (ERROR: No se puede utilizar el Administrador corporativo de SQL para crear procedimientos almacenados que contienen objetos de servidor vinculados). La solución alternativa es incluir la
ANSI_NULLS
opción en el cuadro de diálogo del procedimiento almacenado:fuente
ANSI_NULLS ON
hace una enorme diferencia de rendimiento.JOIN
cláusulas dentro de la vista tienen un significado diferente cuandoANSI_NULLS OFF
. De repente, las filas coinciden, lo que hace que el optimizador ejecute la consulta de manera completamente diferente. Imagine que en lugar de eliminar el 99.9% de todas las filas, de repente regresan.ANSI_NULLS OFF
está en desuso y se considera una mala prácticaHaga esto para su base de datos. Tengo el mismo problema: funciona bien en una base de datos, pero cuando copio esta base de datos a otra usando Importar SSIS (no la restauración habitual), este problema ocurre en la mayoría de mis procedimientos almacenados. Entonces, después de buscar en Google un poco más, encontré el blog del Pinal Dave (que por cierto, encontré la mayor parte de su publicación y me ayudó mucho, así que gracias Pinal Dave) .
Ejecuté la consulta a continuación en mi base de datos y corrigió mi problema:
Espero que esto ayude. Simplemente pasando la ayuda de otros que me ayudaron.
fuente
DBCC REINDEX
ha quedado en desuso, por lo que debe buscar alternativas.DBCC DBREINDEX
MS dice: "Esta característica se eliminará en una versión futura de Microsoft SQL Server. No use esta característica en nuevos trabajos de desarrollo y modifique las aplicaciones que actualmente usan esta característica lo antes posible. Utilice ALTER INDEX".Estaba enfrentando el mismo problema y esta publicación fue muy útil para mí, pero ninguna de las respuestas publicadas resolvió mi problema específico. Quería publicar la solución que funcionó para mí con la esperanza de que pueda ayudar a alguien más.
https://stackoverflow.com/a/24016676/814299
fuente
Esta vez encontraste tu problema. Si la próxima vez tiene menos suerte y no puede resolverlo, puede usar la congelación del plan y dejar de preocuparse por un plan de ejecución incorrecto.
fuente
Estaba experimentando este problema. Mi consulta se parecía a:
Mi procedimiento almacenado se definió como:
¡Cambié el tipo de datos a datetime y listo! ¡Pasé de 30 minutos a 1 minuto!
fuente
¿Has intentado reconstruir las estadísticas y / o los índices en la tabla Report_Opener? Todas las recomendaciones del SP no valdrán nada si las estadísticas aún muestran datos de la primera inauguración de la base de datos.
La consulta inicial en sí misma funciona rápidamente porque el optimizador puede ver que el parámetro nunca será nulo. En el caso del SP, el optimizador no puede estar seguro de que el parámetro nunca será nulo.
fuente
Aunque generalmente estoy en contra (aunque en este caso parece que tiene una razón genuina), ¿ha intentado proporcionar alguna sugerencia de consulta sobre la versión SP de la consulta? Si SQL Server está preparando un plan de ejecución diferente en esas dos instancias, ¿puede usar una pista para decirle qué índice usar, para que el plan coincida con el primero?
Para algunos ejemplos, puedes ir aquí .
EDITAR: Si puede publicar su plan de consulta aquí, tal vez podamos identificar alguna diferencia entre los planes que dicen.
SEGUNDO: Se actualizó el enlace para que sea específico de SQL-2000. Tendrás que desplazarte hacia abajo, pero hay un segundo titulado "Sugerencias de tabla" que es lo que estás buscando.
TERCERO: La consulta "Mala" parece estar ignorando el [IX_Openers_SessionGUID] en la tabla "Openers". ¿Alguna posibilidad de agregar una pista INDEX para forzarlo a usar ese índice cambiará las cosas?
fuente
Esto es probablemente improbable, pero dado que su comportamiento observado es inusual, debe verificarse y nadie más lo ha mencionado.
¿Estás absolutamente seguro de que todos los objetos son propiedad de dbo y no tienes copias falsas de tu propiedad o de un usuario diferente?
Solo ocasionalmente, cuando he visto un comportamiento extraño, es porque en realidad había dos copias de un objeto y cuál obtienes depende de lo que se especifique y de con quién hayas iniciado sesión. Por ejemplo, es perfectamente posible tener dos copias de una vista o procedimiento con el mismo nombre pero propiedad de diferentes propietarios, una situación que puede surgir cuando no está conectado a la base de datos como un dbo y olvida especificar dbo como propietario del objeto cuando Tú creas el objeto.
Tenga en cuenta que en el texto está ejecutando algunas cosas sin especificar el propietario, por ejemplo
si, por ejemplo, hay dos copias de viewOpener presentes propiedad de dbo y [algún otro usuario], entonces cuál recompilar si no especifica depende de las circunstancias. Lo mismo ocurre con la vista Report_Opener: si hay dos copias (y podrían diferir en la especificación o el plan de ejecución), lo que se usa depende de las circunstancias, y como no especifica el propietario, es perfectamente posible que su consulta ad hoc use una y el procedimiento compilado podría usar use the other.
Como digo, probablemente sea poco probable, pero es posible y debe verificarse porque sus problemas podrían ser que simplemente está buscando el error en el lugar incorrecto.
fuente
Esto puede sonar tonto y parece obvio por el nombre SessionGUID, pero ¿es la columna un identificador único en Report_Opener? De lo contrario, puede intentar convertirlo al tipo correcto y darle una oportunidad o declarar su variable al tipo correcto.
El plan creado como parte del sproc puede funcionar de manera poco intuitiva y realizar un reparto interno en una mesa grande.
fuente
varchar
columna con unnvarchar
valor (por ejemploWHERE CustomerName = N'zrendall'
). SQL Server tuvo que convertir cada valor de columna anvarchar
antes de la comparación.Tengo otra idea ¿Qué pasa si crea esta función basada en tablas?
Y luego seleccionó de él usando la siguiente declaración (incluso poniendo esto en su SP):
Parece que lo que está sucediendo (que todo el mundo ya ha comentado) es que SQL Server simplemente asume que algo está mal, y tal vez esto lo obligará a corregir la suposición. Odio agregar el paso adicional, pero no estoy seguro de qué más podría estar causándolo.
fuente
- Aquí está la solución:
-- Eso es
fuente