Considérez ces deux fonctions:
ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)
ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)
Autant que je sache, ils produisent exactement le même résultat. En d'autres termes, l'ordre dans lequel vous répertoriez les colonnes de la PARTITION BY
clause n'a pas d'importance.
S'il y a un index, (A,B,C)
je m'attendais à ce que l'optimiseur utilise cet index dans les deux variantes.
Mais, étonnamment, l'optimiseur a décidé de faire un tri extra explicite dans la deuxième variante.
Je l'ai vu sur SQL Server 2008 Standard et SQL Server 2014 Express.
Voici un script complet que j'ai utilisé pour le reproduire.
Testé sur Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 février 2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64 bits) sur Windows NT 6.1 (Build 7601: Service Pack 1)
et Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 mai 2016 15:33:17 Copyright (c) Microsoft Corporation Express Edition (64 bits) sur Windows NT 6.1 (Build 7601: Service Pack 1)
avec l'ancien et le nouveau Cardinality Estimator en utilisant OPTION (QUERYTRACEON 9481)
et OPTION (QUERYTRACEON 2312)
.
Configurer la table, l'index et les exemples de données
CREATE TABLE [dbo].[T](
[ID] [int] IDENTITY(1,1) NOT NULL,
[A] [int] NOT NULL,
[B] [int] NOT NULL,
[C] [int] NOT NULL,
CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
[A] ASC,
[B] ASC,
[C] ASC
)WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON)
GO
INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);
Requêtes
SELECT -- AB
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);
SELECT -- BA
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);
SELECT -- both
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);
Plans d'exécution
PARTITION PAR A, B
PARTITION PAR B, A
Tous les deux
Comme vous pouvez le voir, le deuxième plan a un tri supplémentaire. Il ordonne par B, A, C. L'optimiseur, apparemment, n'est pas assez intelligent pour réaliser que PARTITION BY B,A
c'est la même chose PARTITION BY A,B
et pour trier à nouveau les données.
Fait intéressant, la troisième requête contient les deux variantes ROW_NUMBER
et il n'y a pas de tri supplémentaire! Le plan est le même que pour la première requête. (Le projet de séquence a une expression supplémentaire dans la liste de sortie pour la colonne supplémentaire, mais pas de tri supplémentaire). Donc, dans ce cas plus compliqué, l'optimiseur semblait être assez intelligent pour réaliser que PARTITION BY B,A
c'est la même chose que PARTITION BY A,B
.
Dans les première et troisième requêtes, l'opérateur Index Scan a la propriété Ordered: True, dans la deuxième requête, il est False.
Encore plus intéressant, si je réécris la troisième requête comme ceci (permutez deux colonnes):
SELECT -- both
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);
alors le tri supplémentaire apparaît à nouveau!
Quelqu'un pourrait-il faire la lumière? Que se passe-t-il ici dans l'optimiseur?
la source
Réponses:
Il semble qu'il n'y ait pas de bonne "réponse" définitive à la question "que se passe-t-il dans l'optimiseur", à moins que vous ne soyez son développeur et que vous connaissiez ses internes.
Je vais rassembler les commentaires ici.
Dans l'ensemble, il semble qu'il serait trop difficile de l'appeler un bogue, car le résultat final de la requête est correct. Dans certains cas, le plan d'exécution n'est tout simplement pas optimal. ypercubeᵀᴹ , Martin Smith et Aaron Bertrand l' appellent "optimisation manquée".
Il existe un article quelque peu apparenté sur les index décroissants. Ordre des index, parallélisme et calculs de classement par Itzik Ben-Gan. Itzik y discute des index descendants et donne également un exemple de la façon dont la direction de la définition d'index affecte les fonctions de la fenêtre avec les partitions. Il montre des exemples de requêtes et de plans générés avec
ROW_NUMBER
un opérateur de tri supplémentaire que l'optimiseur aurait pu éviter.Pour moi, le résultat pratique serait de garder à l’esprit cette particularité d’optimiseur. Lorsque vous utilisez
PARTITION BY
des fonctions dans la fenêtre, essayez toujours de faire correspondre l'ordre dans lequel vous répertoriez les colonnesPARTITION BY
avec l'ordre dans lequel elles sont répertoriées dans l'index. Même si cela n'a pas d'importance.Un autre aspect de cette précaution est lorsque vous examinez vos index et décidez de permuter certaines colonnes dans la définition d'index. Sachez que vous pouvez par inadvertance affecter certaines requêtes existantes qui ne devraient apparemment pas être affectées. C'est en fait ainsi que j'ai remarqué cette particularité de l'optimiseur.
Si vous ne le faites pas, l'optimiseur peut ne pas être en mesure d'utiliser l'index à son plein potentiel. Même si l'optimiseur choisit un plan optimal, ce plan peut devenir moins optimal avec une moindre modification innocente de la requête, comme changer l'ordre des colonnes dans l'
SELECT
instruction.la source