Une requête est rapide:
_DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
_
coût de la sous-arborescence: 0.502
Mais mettre le même SQL dans une procédure stockée est lent et avec un plan d'exécution totalement différent
_CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
EXECUTE ViewOpener @SessionGUID
_
Coût de la sous-arborescence: 19.2
J'ai couru
_sp_recompile ViewOpener
_
Et il fonctionne toujours (mal), et j'ai également changé la procédure stockée pour
_CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *, 'recompile please'
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
_
Et à nouveau, essayant de le tromper pour le recompiler.
J'ai abandonné et recréé la procédure stockée afin de générer un nouveau plan.
J'ai essayé de forcer les recompilations, et empêcher le reniflement des paramètres, en utilisant une variable leurre:
_CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
DECLARE @SessionGUIDbitch uniqueidentifier
SET @SessionGUIDbitch = @SessionGUID
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUIDbitch
ORDER BY CurrencyTypeOrder, Rank
_
J'ai également essayé de définir la procédure stockée WITH RECOMPILE
:
_CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier
WITH RECOMPILE
AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
_
Pour que son plan ne soit jamais mis en cache, et j'ai essayé de forcer une recompilation à exécuter:
_EXECUTE ViewOpener @SessionGUID WITH RECOMPILE
_
Ce qui n'a pas aidé.
J'ai essayé de convertir la procédure en SQL dynamique:
_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
_
Ce qui n'a pas aidé.
L'entité "_Report_Opener
_" est une vue non indexée. La vue ne fait référence qu'à des tables sous-jacentes. Aucune table ne contient de colonnes calculées, indexées ou non.
Pour le plaisir j'ai essayé de créer la vue avec
_SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON
_
Cela n'a pas résolu le problème.
Comment est-ce que
J'ai essayé de déplacer la définition de la vue directement dans la procédure stockée (violation de 3 règles de gestion et rupture d'une encapsulation importante), ce qui la ralentit de 6 fois environ.
Pourquoi la version de la procédure stockée est-elle si lente? Qu'est-ce qui peut éventuellement expliquer que SQL Server exécute du SQL ad hoc plus rapidement qu'un autre type de SQL ad hoc?
Je préférerais vraiment pas
changer le code du tout
_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)
_
Mais qu'est-ce qui peut expliquer que SQL Server ne puisse pas s'exécuter aussi rapidement que SQL Server exécutant une requête, sinon le paramètre sniffing.
Ma prochaine tentative consistera à appeler StoredProcedureA
appeler StoredProcedureB
appeler StoredProcedureC
appeler StoredProcedureD
pour interroger la vue.
Et à défaut, faites en sorte que la procédure stockée appelle une procédure stockée, appelle un fichier UDF, appelle un fichier UDF, appelle une procédure stockée, appelle un fichier UDF pour interroger la vue.
En résumé, l’exécution suivante est rapide à partir de QA, mais lente lorsqu’elle est insérée dans une procédure stockée:
L'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)
_
Plans d'exécution
Le bon 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)
_
Le mauvais 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)
_
Le méchant est entrain de spouler 6 millions de lignes; l'autre ne l'est pas.
Note: Ce n'est pas une question de réglage d'une requête. J'ai une requête qui court comme un éclair. Je veux juste que SQL Server s'exécute rapidement à partir d'une procédure stockée.
J'ai trouvé le problème, voici le script des versions lente et rapide de la procédure stockée:
dbo.ViewOpener__RenamedForCruachan__Slow.PRC
_SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Slow
@SessionGUID uniqueidentifier
AS
SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
_
dbo.ViewOpener__RenamedForCruachan__Fast.PRC
_SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Fast
@SessionGUID uniqueidentifier
AS
SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
_
Si vous n'avez pas remarqué la différence, je ne vous en veux pas. La différence n'est pas du tout dans la procédure stockée. La différence qui transforme une requête rapide de coût 0,5 en une requête qui génère une file d'attente enthousiaste de 6 millions de lignes:
Lent: _SET ANSI_NULLS OFF
_
Rapide: _SET ANSI_NULLS ON
_
Cette réponse pourrait également avoir un sens, car la vue contient une clause de jointure qui dit:
_(table.column IS NOT NULL)
_
Donc, il y a quelques NULL
s impliqués.
L’explication est ensuite prouvée en revenant à Query Analizer et en exécutant
_SET ANSI_NULLS OFF
_
.
_DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'
_
.
_SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
_
Et la requête est lente.
Le problème n'est donc pas car la requête est en cours d'exécution à partir d'une procédure stockée. Le problème est que l'option de connexion par défaut d'Enterprise Manager est _ANSI_NULLS off
_, au lieu de _ANSI_NULLS on
_, qui est la valeur par défaut du contrôle qualité.
Microsoft reconnaît ce fait dans KB296769 (BOGUE: impossible d'utiliser SQL Enterprise Manager pour créer des procédures stockées contenant des objets serveur liés). La solution de contournement consiste à inclure l'option _ANSI_NULLS
_ dans la boîte de dialogue de procédure stockée:
_Set ANSI_NULLS ON
Go
Create Proc spXXXX as
....
_
J'ai eu le même problème que l'affiche originale mais la réponse citée ne m'a pas résolu le problème. La requête fonctionnait toujours très lentement à partir d'une procédure stockée.
J'ai trouvé une autre réponse ici "Parameter Sniffing" , merci Omnibuzz. Réduire à utiliser des "variables locales" dans vos requêtes de procédure stockée, mais lisez l'original pour plus de compréhension, c'est une excellente rédaction. par exemple.
Façon lente:
CREATE PROCEDURE GetOrderForCustomers(@CustID varchar(20))
AS
BEGIN
SELECT *
FROM orders
WHERE customerid = @CustID
END
Voie rapide:
CREATE PROCEDURE GetOrderForCustomersWithoutPS(@CustID varchar(20))
AS
BEGIN
DECLARE @LocCustID varchar(20)
SET @LocCustID = @CustID
SELECT *
FROM orders
WHERE customerid = @LocCustID
END
En espérant que cela aide quelqu'un d'autre, cela a réduit mon temps d'exécution de plus de 5 minutes à environ 6-7 secondes.
Faites cela pour votre base de données. J'ai le même problème - cela fonctionne bien dans une base de données, mais lorsque je copie cette base de données dans une autre à l'aide de SSIS Import (pas la restauration habituelle), ce problème se produit dans la plupart de mes procédures stockées. Donc, après avoir googlé un peu plus, j'ai trouvé le blog de Pinal Dave (d'ailleurs, j'ai rencontré la plupart de ses publications et m'a beaucoup aidé, donc merci Pinal Dave) .
J'exécute la requête ci-dessous sur ma base de données et cela corrige mon problème:
EXEC sp_MSforeachtable @command1="print '?' DBCC DBREINDEX ('?', ' ', 80)"
GO
EXEC sp_updatestats
GO
J'espère que cela t'aides. Juste passer l'aide des autres qui m'ont aidé.
Je faisais face au même problème et ce message m'a été très utile, mais aucune des réponses postées n'a résolu mon problème spécifique. Je voulais publier la solution qui fonctionnait pour moi dans l'espoir d'aider quelqu'un d'autre.
https://stackoverflow.com/a/24016676/814299
A la fin de votre requête, ajoutez OPTION (OPTIMIZE FOR (@now UNKNOWN))
Je rencontrais ce problème. Ma requête ressemblait à quelque chose comme:
select a, b, c from sometable where date > '20140101'
Ma procédure stockée a été définie comme:
create procedure my_procedure (@dtFrom date)
as
select a, b, c from sometable where date > @dtFrom
J'ai changé le type de données en datetime et le tour est joué! Passé de 30 minutes à 1 minute!
create procedure my_procedure (@dtFrom datetime)
as
select a, b, c from sometable where date > @dtFrom
Cette fois, vous avez trouvé votre problème. Si vous avez moins de chance la prochaine fois et que vous ne pouvez pas vous en rendre compte, vous pouvez utiliser gel du plan et cesser de vous inquiéter du mauvais plan d'exécution.
Avez-vous essayé de reconstruire les statistiques et/ou les index de la table Report_Opener. Toutes les modifications de SP n'auront aucune valeur si les statistiques affichent toujours les données de la première utilisation de la base de données.
La requête initiale elle-même fonctionne rapidement car l'optimiseur peut voir que le paramètre ne sera jamais nul. Dans le cas de SP, l'optimiseur ne peut pas être sûr que le paramètre ne sera jamais nul.
Cela peut paraître idiot et semble évident du nom SessionGUID, mais la colonne est-elle un identifiant unique sur Report_Opener? Sinon, vous pouvez essayer de le transtyper avec le bon type et de lui donner une chance ou de déclarer votre variable au bon type.
Le plan créé dans le cadre du sproc peut ne pas être intuitif et créer une distribution interne sur une grande table.
Ceci est probablement improbable, mais étant donné que votre comportement observé est inhabituel, vous devez le vérifier et personne d'autre ne l'a mentionné.
Etes-vous absolument sûr que tous les objets sont la propriété de dbo et que vous ne possédez pas non plus de copies douteuses appartenant à vous-même ou à un autre utilisateur?
Parfois, quand j'ai vu un comportement étrange, c'est parce qu'il y avait en fait deux copies d'un objet et celle que vous obtenez dépend de ce qui est spécifié et de la personne avec laquelle vous êtes connecté. Par exemple, il est parfaitement possible d’avoir deux copies d’une vue ou d’une procédure portant le même nom mais appartenant à des propriétaires différents - une situation pouvant survenir lorsque vous n'êtes pas connecté à la base de données en tant que dbo et oubliez de spécifier dbo en tant que propriétaire de l'objet lorsque vous créez l'objet.
Notez que dans le texte, vous exécutez certaines choses sans spécifier le propriétaire, par exemple
sp_recompile ViewOpener
si, par exemple, il existe deux copies de viewOpener appartenant à dbo et à [un autre utilisateur], la copie que vous recompilez si vous ne le spécifiez pas dépend des circonstances. Idem avec la vue Report_Opener - s’il existe deux copies (et que leur spécification ou leur plan d’exécution peut différer), alors ce qui est utilisé dépend des circonstances - et comme vous ne spécifiez pas le propriétaire, il est parfaitement possible que votre requête ad hoc en utilise une et procédure compilée peut utiliser utiliser l'autre.
Comme je l’ai dit, c’est probablement peu probable, mais c’est possible et vous devriez le vérifier, car vous pourriez peut-être simplement chercher le bogue au mauvais endroit.
Bien que je sois généralement contre (dans ce cas, il semble que vous ayez une véritable raison), avez-vous essayé de fournir des indications de requête sur la version SP de la requête? Si SQL Server prépare un plan d'exécution différent dans ces deux cas, pouvez-vous utiliser un indice pour lui indiquer quel index utiliser, afin que le plan corresponde au premier?
Pour quelques exemples, vous pouvez aller ici .
EDIT: Si vous pouvez publier votre plan de requête ici, nous pourrons peut-être identifier une différence entre les plans en question.
DEUXIEME: Mise à jour du lien pour qu'il soit spécifique à SQL-2000. Vous devrez faire défiler un chemin, mais il y a un deuxième intitulé "Conseils de table", c'est ce que vous recherchez.
TROISIÈME: La requête "Bad" semble ignorer le [IX_Openers_SessionGUID] de la table "Openers" - une chance d'ajouter un indicateur INDEX pour le forcer à utiliser cet index changera-t-elle les choses?
J'ai une autre idée. Et si vous créez cette fonction sous forme de table:
CREATE FUNCTION tbfSelectFromView
(
-- Add the parameters for the function here
@SessionGUID UNIQUEIDENTIFIER
)
RETURNS TABLE
AS
RETURN
(
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
)
GO
Et puis sélectionné en utilisant la déclaration suivante (même en mettant cela dans votre SP):
SELECT *
FROM tbfSelectFromView(@SessionGUID)
Il semblerait que ce qui se passe (ce que tout le monde a déjà commenté) est que SQL Server fait une hypothèse erronée, ce qui l’obligera peut-être à la corriger. Je déteste ajouter cette étape supplémentaire, mais je ne suis pas sûr de ce qui pourrait la causer.
- Voici la solution:
create procedure GetOrderForCustomers(@CustID varchar(20))
as
begin
select * from orders
where customerid = ISNULL(@CustID, '')
end
-- C'est ça