J'essaie de faire une requête que notre application envoie plus efficacement. J'ai légèrement modifié la requête dans SSMS et il s'exécutera dans environ 1 seconde.
Interroger un
SELECT O.Code AS 'Code', O.[Action] AS 'Action',
SUM(OpenResponseWithin) AS 'OpenResponseWithin',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(OpenResponseWithin))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'OpenResponseWithinPercentage',
SUM(OpenResponseAfter) AS 'OpenResponseAfter',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(OpenResponseAfter))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'OpenResponseAfterPercentage',
(SUM(OpenResponseWithin)+SUM(OpenResponseAfter)) AS 'OpenTotal',
SUM(CloseResponseWithin) AS 'CloseResponseWithin',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(CloseResponseWithin))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'CloseResponseWithinPercentage',
SUM(CloseResponseAfter) AS 'CloseResponseAfter',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(CloseResponseAfter))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'CloseResponseAfterPercentage',
SUM(CloseNever) AS 'CloseNever',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(CloseNever))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'CloseNeverPercentage'
FROM Custom_OpenCodeMRC O
WHERE O.ActionDate BETWEEN '10/5/2017' AND '12/4/2017'
AND
O.Code IN ('BERZ20','BERZ21','BERZ24','BERZ50','FTHZ63','YOR56','YOR57')
GROUP BY O.Code,O.[Action]
ORDER BY O.Code,O.[Action]
Si je quitte la requête la façon dont l'application le transmet avec des paramètres, il faut au moins 20 secondes pour exécuter.
Requête b
DECLARE @parm1 NVARCHAR(10) = '10/05/2017';
DECLARE @parm2 NVARCHAR(10) = '12/04/2017';
DECLARE @parm9 NVARCHAR(6) = 'BERZ20';
DECLARE @parm8 NVARCHAR(6) = 'BERZ21';
DECLARE @parm7 NVARCHAR(6) = 'BERZ24';
DECLARE @parm6 NVARCHAR(6) = 'BERZ50';
DECLARE @parm5 NVARCHAR(6) = 'FTHZ63';
DECLARE @parm4 NVARCHAR(5) = 'YOR56';
DECLARE @parm3 NVARCHAR(5) = 'YOR57';
SELECT O.Code AS 'Code',O.[Action] AS 'Action',
SUM(OpenResponseWithin) AS 'OpenResponseWithin',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(OpenResponseWithin))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'OpenResponseWithinPercentage',
SUM(OpenResponseAfter) AS 'OpenResponseAfter',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(OpenResponseAfter))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'OpenResponseAfterPercentage',
(SUM(OpenResponseWithin)+SUM(OpenResponseAfter)) AS 'OpenTotal',
SUM(CloseResponseWithin) AS 'CloseResponseWithin',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(CloseResponseWithin))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'CloseResponseWithinPercentage',
SUM(CloseResponseAfter) AS 'CloseResponseAfter',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(CloseResponseAfter))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'CloseResponseAfterPercentage',
SUM(CloseNever) AS 'CloseNever',
CONVERT(VARCHAR,convert(decimal(10,2),(((SUM(CloseNever))*100))/convert(decimal(10,2),(SUM(OpenResponseWithin)+SUM(OpenResponseAfter))))) + '%' AS 'CloseNeverPercentage'
FROM Custom_OpenCodeMRC O
WHERE O.ActionDate BETWEEN @parm1 AND @parm2
AND
O.Code IN (@parm3,@parm4,@parm5,@parm6,@parm7,@parm8,@parm9)
GROUP BY O.Code,O.[Action]
ORDER BY O.Code Asc,O.[Action] Asc
J'ai les plans d'exécution sauvés, mais ils sont très longs. Si quelqu'un aimerait les voir, je peux poster les plans en XML. Voici quelques statistiques de requête.
+-----------+---------------------+
| Query A | CPU time = 1439 ms |
| Query B | CPU time = 23282 ms |
+-----------+---------------------+
ET
+----------------+--------------+--------------+---------------+---------------+
| Table | Query A | Query B | Query A | Query B |
| | Scan Count | Scan Count | Logical Reads | Logical Reads |
+----------------+--------------+--------------+---------------+---------------+
| ResponseAction | 1 | 1 | 2 | 2 |
| Code | 7 | 5 | 14 | 13 |
| Workfile | 0 | 0 | 0 | 0 |
| Worktable | 57 | 1,305 | 30,712 | 1,735,281 |
| Response | 7 | 7 | 12,1136 | 12,1137 |
| ResponseHistory| 24 | 23 | 3,507 | 3,507 |
| Ticket | 5 | 5 | 907 | 907 |
| Organization | 0 | 5 | 3,479 | 1,463 |
+----------------+--------------+--------------+---------------+---------------+
Ils ont tous les deux exactement les mêmes résultats, mais des temps d'exécution très différents. Quelqu'un peut-il s'il vous plaît m'expliquer pourquoi la requête A utilise un plan d'exécution beaucoup plus efficace que la requête B? Merci!
Voici des liens vers les plans d'exécution:
Imaginez que vous devez planifier un voyage à un emplacement mystérieux. Cela pourrait être n'importe où dans le monde. Vos options pour voyager sont à pied, en voiture ou en avion. Vous devez choisir avant de connaître la destination. Que choisiriez-vous? Je choisirais voyager en avion. C'est la meilleure option si vous prenez le temps de trajet moyen pour toutes les options possibles du monde. Bien sûr, vous pourriez avoir malchanceux si votre destination dis-elle, dans la rue. Prendre un avion sera relativement inefficace pour cette destination. D'autre part, c'est certainement une meilleure option que nécessaire pour marcher des milliers de kilomètres.
L'utilisation de variables locales dans les requêtes met souvent l'optimiseur de la requête dans le même type de situation. L'optimiseur de requête vise à créer un plan mis en cache qui fonctionne bien pour toutes les variables d'entrée possibles. Il utilisera souvent la densité de l'objet statistique, qui est un moyen d'obtenir une estimation de la cardinalité "moyenne". Par défaut, il ne fera pas incorporer la valeur de vos paramètres spécifiques dans la requête et utiliser ces valeurs pour créer un plan efficace.
Pour une autre façon de l'examiner, vos structures de données et de table pourraient signifier qu'un plan de requête fonctionne bien pour traiter une quantité relativement faible de données, mais un plan de requête différent fonctionne bien pour traiter une quantité relativement importante de données. Par exemple, vous voudrez peut-être que le plan de requête change si la valeur de @parm1
passe à partir de '10/05/2017 'à '10/05/2000'. Avec des variables locales, vous obtenez juste un seul plan mis en cache. Ce plan en cache entraînera moins que des performances optimales pour une ou les deux valeurs de date.
La solution la plus simple pour améliorer les performances de la requête B consiste à ajouter un indice RECOMPILE
. Cela active l'optimisation optimisation de paramètres qui vous donne un plan personnalisé en fonction des valeurs de variable au moment de l'exécution. L'inconvénient de RECOMPILE
est que l'optimiseur de requête doit compiler un nouveau plan de requête à chaque fois. Si votre bonne requête prend une seconde pour courir, vous n'avez probablement pas trop à vous inquiéter à ce sujet.