Une requête s'exécute rapidement:
DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
coût sous-arbre: 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 du sous-arbre: 19,2
J'ai couru
sp_recompile ViewOpener
Et il fonctionne toujours de la même manière (mal), et j'ai également changé la procédure stockée en
CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *, 'recompile please'
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
Et encore une fois, en essayant de vraiment le tromper en recompilation.
J'ai supprimé et recréé la procédure stockée afin de générer un nouveau plan.
J'ai essayé de forcer les recompilations et d'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, j'ai essayé de forcer une recompilation lors de l'exécution:
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 qui n'est pas indexée. La vue fait uniquement référence aux tables sous-jacentes. Aucune table ne contient de colonnes calculées, indexées ou non.
Pour l'enfer, j'ai essayé de créer la vue avec
SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON
Cela ne l'a pas corrigé.
Comment se fait-il que
- la requête est rapide
- déplacer la requête vers une vue et effectuer une sélection dans la vue est rapide
- la sélection dans la vue à partir d'une procédure stockée est 40 fois plus lente?
J'ai essayé de déplacer la définition de la vue directement dans la procédure stockée (violant 3 règles métier et cassant une encapsulation importante), ce qui ne la rend que 6 fois plus lente.
Pourquoi la version de la procédure stockée est-elle si lente? Qu'est-ce qui peut expliquer que SQL Server exécute un SQL ad hoc plus rapidement qu'un autre type de SQL ad hoc?
Je préfère vraiment ne pas
- intégrer le SQL dans le code
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 Sever exécutant une requête, sinon le reniflage de paramètres.
Ma prochaine tentative sera d'avoir StoredProcedureA
appel StoredProcedureB
appel StoredProcedureC
appel StoredProcedureD
pour interroger la vue.
Et à défaut, demandez à la procédure stockée d'appeler une procédure stockée, d'appeler un UDF, d'appeler un UDF, d'appeler une procédure stockée, d'appeler un UDF pour interroger la vue.
Pour résumer, les éléments suivants s'exécutent rapidement depuis QA, mais sont lents lorsqu'ils sont placés 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 mauvais est impatient de mettre en file d'attente 6 millions de lignes; l'autre ne l'est pas.
Remarque: il ne s'agit pas de régler une requête. J'ai une requête qui tourne très vite. Je veux juste que SQL Server s'exécute rapidement à partir d'une procédure stockée.
la source
Réponses:
J'ai eu le même problème que l'affiche originale mais la réponse citée n'a pas résolu le problème pour moi. La requête s'exécutait toujours très lentement à partir d'une procédure stockée.
J'ai trouvé une autre réponse ici "Reniflage de paramètres" , merci Omnibuzz. Cela revient à utiliser des "variables locales" dans vos requêtes de procédures stockées, mais lisez l'original pour plus de compréhension, c'est une excellente écriture. par exemple
Chemin lent:
Manière rapide:
J'espère que cela aide quelqu'un d'autre, ce faisant, j'ai réduit mon temps d'exécution de 5+ minutes à environ 6-7 secondes.
la source
WITH RECOMPILE
n'a pas fait de différence pour moi, seulement les paramètres locaux.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
dbo.ViewOpener__RenamedForCruachan__Fast.PRC
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 de coût rapide de 0,5 en une requête qui fait une bobine impatiente de 6 millions de lignes:
Lent:
SET ANSI_NULLS OFF
Vite:
SET ANSI_NULLS ON
Cette réponse pourrait également avoir un sens, car la vue a une clause de jointure qui dit:
Il y a donc des
NULL
implications.L'explication est encore prouvée en revenant à Query Analizer et en exécutant
.
.
Et la requête est lente.
Le problème n'est donc pas dû au fait que la requête est exécutée à partir d'une procédure stockée. Le problème est que l'option par défaut de connexion d'Enterprise Manager est
ANSI_NULLS off
, plutôt queANSI_NULLS on
, qui est la valeur par défaut de QA.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'
ANSI_NULLS
option dans la boîte de dialogue de procédure stockée:la source
ANSI_NULLS ON
fait une énorme différence de performances.JOIN
clauses à l'intérieur de la vue ont une signification différente quandANSI_NULLS OFF
. Soudain, les lignes correspondent, ce qui entraîne l'optimiseur à exécuter la requête complètement différemment. Imaginez qu'au lieu d'éliminer 99,9% de toutes les lignes, elles reviennent soudainement.ANSI_NULLS OFF
est déconseillé et considéré comme une mauvaise pratiqueFaites 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 l'importation SSIS (pas la restauration habituelle), ce problème se produit pour la plupart de mes procédures stockées. Donc, après avoir googlé un peu plus, j'ai trouvé le blog de Pinal Dave (qui d'ailleurs, j'ai rencontré la plupart de ses messages et m'a beaucoup aidé alors merci Pinal Dave) .
J'exécute la requête ci-dessous sur ma base de données et cela a corrigé mon problème:
J'espère que cela t'aides. Je passe juste l'aide des autres qui m'ont aidé.
la source
DBCC REINDEX
a été déprécié, vous devriez donc chercher des alternatives.DBCC DBREINDEX
:, MS dit: "Cette fonctionnalité sera supprimée dans une future version de Microsoft SQL Server. N'utilisez pas cette fonctionnalité dans les nouveaux travaux de développement et modifiez les applications qui utilisent actuellement cette fonctionnalité dès que possible. Utilisez plutôt ALTER INDEX."J'étais confronté au même problème et ce message m'a été très utile, mais aucune des réponses publiées n'a résolu mon problème spécifique. Je voulais publier la solution qui a fonctionné pour moi dans l'espoir qu'elle puisse aider quelqu'un d'autre.
https://stackoverflow.com/a/24016676/814299
la source
Cette fois, vous avez trouvé votre problème. Si la prochaine fois, vous avez moins de chance et ne pouvez pas le comprendre, vous pouvez utiliser le gel du plan et ne plus vous soucier d'un mauvais plan d'exécution.
la source
Je rencontrais ce problème. Ma requête ressemblait à quelque chose comme:
Ma procédure stockée a été définie comme:
J'ai changé le type de données en datetime et le tour est joué! Je suis passé de 30 minutes à 1 minute!
la source
Avez-vous essayé de reconstruire les statistiques et / ou les index sur la table Report_Opener. Toutes les recomplies du SP ne valent rien si les statistiques affichent toujours des données datant de la première mise à jour 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 du SP, l'optimiseur ne peut pas être sûr que le paramètre ne sera jamais nul.
la source
Bien que je sois généralement contre (bien que dans ce cas, il semble que vous ayez une véritable raison), avez-vous essayé de fournir des indices 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 instances, 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 pouvons peut-être identifier une certaine différence entre les plans qui le disent.
DEUXIÈME: mise à jour du lien pour être spécifique à SQL-2000. Vous devrez faire défiler vers le bas de plusieurs façons, mais il y a une seconde intitulée "Tableau Hints" qui est ce que vous recherchez.
TROISIÈME: La requête "Bad" semble ignorer le [IX_Openers_SessionGUID] sur la table "Openers" - toute chance d'ajouter un indice INDEX pour le forcer à utiliser cet index changera-t-il les choses?
la source
C'est probablement peu probable, mais étant donné que votre comportement observé est inhabituel, il doit être vérifié et personne d'autre ne l'a mentionné.
Êtes-vous absolument sûr que tous les objets appartiennent à dbo et que vous n'avez pas non plus de copies escrocs appartenant à vous-même ou à un autre utilisateur?
De temps en temps, 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 qui 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 qui peut se produire 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
si, par exemple, là où deux copies de viewOpener sont détenues par dbo et [un autre utilisateur], celle que vous recompilerez si vous ne spécifiez pas dépend des circonstances. Idem avec la vue Report_Opener - s'il y a deux copies (et dont la spécification ou le plan d'exécution peuvent 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 adhoc en utilise une et la procédure compilée peut utiliser utiliser l'autre.
Comme je l'ai dit, c'est probablement peu probable mais c'est possible et devrait être vérifié car vos problèmes pourraient être que vous recherchez simplement le bogue au mauvais endroit.
la source
Cela peut sembler idiot et semble évident d'après le nom SessionGUID, mais la colonne est-elle un identifiant unique sur Report_Opener? Si ce n'est pas le cas, vous voudrez peut-être essayer de le convertir dans le type correct et lui donner un coup de feu ou déclarer votre variable au type correct.
Le plan créé dans le cadre du sproc peut ne pas fonctionner de manière intuitive et faire un casting interne sur une grande table.
la source
varchar
colonne avec unenvarchar
valeur (par exempleWHERE CustomerName = N'zrendall'
). SQL Server a dû convertir chaque valeur de colonne en unnvarchar
avant la comparaison.J'ai une autre idée. Et si vous créez cette fonction basée sur une table:
Et puis sélectionné à partir de celui-ci en utilisant la déclaration suivante (même en mettant cela dans votre SP):
Il semble que ce qui se passe (ce que tout le monde a déjà commenté) est que SQL Server fait simplement une hypothèse quelque part qui ne va pas, et cela l'obligera peut-être à corriger l'hypothèse. Je déteste ajouter l'étape supplémentaire, mais je ne sais pas quoi d'autre pourrait en être la cause.
la source
- Voici la solution:
-- C'est tout
la source