Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

L'attribution de paramètres d'entrée de procédure stockée à des variables locales permet-elle d'optimiser la requête ?

Je n'essaierai pas d'expliquer tous les détails du reniflage de paramètres, mais en bref, non, il ne le fait pas toujours aider (et cela peut gêner).

Imaginez une table (T) avec une clé primaire et une colonne Date indexée (A), dans la table il y a 1 000 lignes, 400 ont la même valeur de A (disons aujourd'hui 20130122), les 600 lignes restantes sont les 600 prochains jours , donc seulement 1 enregistrement par date.

Cette requête :

SELECT *
FROM T
WHERE A = '20130122';

Donnera un plan d'exécution différent pour :

SELECT *
FROM T
WHERE A = '20130123';

Étant donné que les statistiques indiqueront que pour les 400 premières lignes sur 1 000 seront renvoyées, l'optimiseur doit reconnaître qu'un balayage de table sera plus efficace qu'une recherche de signet, alors que le second ne donnera que 1 lignes, donc une recherche de signet sera beaucoup plus efficace.

Maintenant, revenons à votre question, si nous en faisions une procédure :

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

Puis lancez

EXECUTE dbo.GetFromT '20130122'; --400 rows

Le plan de requête avec le balayage de table sera utilisé, si la première fois que vous l'exécutez, vous utilisez '20130123' comme paramètre, il stockera le plan de recherche de signet. Jusqu'à ce que la procédure soit recompilée, le plan restera le même. Faire quelque chose comme ça :

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

Ensuite, ceci est exécuté :

EXECUTE dbo.GetFromT '20130122';

Bien que la procédure soit compilée en une seule fois, elle ne se déroule pas correctement, donc le plan de requête créé à la première compilation n'a aucune idée que @Param2 deviendra le même que @param, donc l'optimiseur (sans savoir combien de lignes attendre) supposera que 300 seront renvoyés (30 %), car cela jugera une analyse de table plus efficace qu'une recherche de signet. Si vous exécutiez la même procédure avec '20130123' comme paramètre, cela donnerait le même plan (quel que soit le paramètre avec lequel il a été invoqué pour la première fois) car les statistiques ne peuvent pas être utilisées pour une valeur inconnue. Donc, exécuter cette procédure pour '20130122' serait plus efficace, mais pour toutes les autres valeurs, ce serait moins efficace que sans paramètres locaux (en supposant que la procédure sans paramètres locaux ait d'abord été invoquée avec autre chose que '20130122')

Quelques requêtes à démontrer afin que vous puissiez voir les plans d'exécution par vous-même

Créer un schéma et des exemples de données

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

Procédures d'exécution (montrant le plan d'exécution réel) :

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

Vous verrez que la première fois GetFromT est compilé, il utilise un balayage de table et le conserve lorsqu'il est exécuté avec le paramètre '20130122', GetFromT2 utilise également un balayage de table et conserve le plan pour '20130122'.

Une fois que les procédures ont été définies pour la recompilation et exécutées à nouveau (notez dans un ordre différent), GetFromT utilise une boucle de signet et conserve le plan pour '20130122', bien qu'il ait précédemment estimé qu'un balayage de table était un plan plus approprié. GetFromT2 n'est pas affecté par la commande et a le même plan qu'avant la recompilation.

Donc, en résumé, cela dépend de la distribution de vos données, de vos index, de votre fréquence de recompilation et d'un peu de chance pour savoir si une procédure bénéficiera de l'utilisation de variables locales. Ce n'est certainement pas toujours aide.

J'espère avoir fait la lumière sur l'effet de l'utilisation de paramètres locaux, de plans d'exécution et de la compilation de procédures stockées. Si j'ai complètement échoué ou manqué un point clé, une explication beaucoup plus détaillée peut être trouvée ici :

http://www.sommarskog.se/query-plan-mysteres.html