Sac de sable
En travaillant sur des articles de blog de qualité supérieure®, je suis tombé sur un comportement d'optimiseur que je trouvais vraiment exaspérant et intéressant. Je n'ai pas immédiatement d'explication, du moins pas avec qui je suis satisfait, alors je le mets ici au cas où quelqu'un intelligent se présenterait.
Si vous souhaitez suivre, vous pouvez récupérer la version 2013 du vidage de données Stack Overflow ici . J'utilise la table Commentaires, avec un index supplémentaire dessus.
CREATE INDEX [ix_ennui] ON [dbo].[Comments] ( [UserId], [Score] DESC );
Query One
Lorsque j'interroge la table comme ça, j'obtiens un plan de requête étrange .
WITH x
AS
(
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score DESC
)
SELECT *
FROM x
WHERE x.Score >= 500;
Le prédicat SARGable sur Score n'est pas poussé à l'intérieur du CTE. C'est dans un opérateur de filtre beaucoup plus tard dans le plan.
Ce que je trouve étrange, car il se ORDER BY
trouve sur la même colonne que le filtre.
Requête deux
Si je change la requête, elle est poussée.
WITH x
AS
(
SELECT c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
)
SELECT TOP 101 *
FROM x
WHERE x.Score >= 500
ORDER BY x.Score DESC;
Le plan de requête change également et s'exécute beaucoup plus rapidement, sans déversement sur le disque. Ils produisent tous deux les mêmes résultats, avec le prédicat lors de l'analyse d'index non cluster.
Requête trois
C'est l'équivalent d'écrire la requête comme ceci:
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
WHERE c.Score >= 500
ORDER BY c.Score DESC;
Query Four
L'utilisation d'une table dérivée obtient le même "mauvais" plan de requête que la requête CTE initiale
SELECT *
FROM ( SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score DESC ) AS x
WHERE x.Score >= 500;
Les choses deviennent encore plus étranges quand ...
Je modifie la requête pour ordonner les données en ordre croissant et le filtre en <=
.
Pour ne pas allonger cette question, je vais tout assembler.
Requêtes
--Derived table
SELECT *
FROM ( SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score ASC ) AS x
WHERE x.Score <= 500;
--TOP inside CTE
WITH x
AS
(
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score ASC
)
SELECT *
FROM x
WHERE x.Score <= 500;
--Written normally
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
WHERE c.Score <= 500
ORDER BY c.Score ASC;
--TOP outside CTE
WITH x
AS
(
SELECT c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
)
SELECT TOP 101 *
FROM x
WHERE x.Score <= 500
ORDER BY x.Score ASC;
Des plans
Notez qu'aucune de ces requêtes ne tire parti de l'index non cluster - la seule chose qui change ici est la position de l'opérateur de filtrage. En aucun cas, le prédicat n'est poussé vers l'accès à l'index.
Une question apparaît!
Y a-t-il une raison pour laquelle un prédicat SARGable peut être poussé dans certains scénarios et pas dans d'autres? Les différences au sein des requêtes triées par ordre décroissant sont intéressantes, mais les différences entre celles et celles qui sont ascendantes sont bizarres.
Pour toute personne intéressée, voici les plans avec seulement un index sur Score
: