ROW_NUMBER () OVER (PARTITION BY B, A ORDER BY C) n'utilise pas d'index sur (A, B, C)

12

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 BYclause 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

UN B

PARTITION PAR B, A

BA

Tous les deux

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,Ac'est la même chose PARTITION BY A,Bet pour trier à nouveau les données.

Fait intéressant, la troisième requête contient les deux variantes ROW_NUMBERet 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,Ac'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?

Vladimir Baranov
la source
Commentaires archivés .
Paul White 9

Réponses:

2

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".

  • Semble GROUP BY a,bet GROUP BY b,adonne des plans identiques mais PARTITION BYne peut pas utiliser la même transformation

  • Il y a aussi d'autres optimisations manquantes où les fonctions de fenêtre avec la même spécification de fenêtre peuvent avoir une opération de tri supplémentaire si elles sont séparées dans la liste de sélection par une avec une spécification différente.

  • Oui, cela semble être une autre optimisation manquée, et il y en a beaucoup. L'optimiseur est écrit par des humains et n'est pas parfait


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_NUMBERun 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 BYdes fonctions dans la fenêtre, essayez toujours de faire correspondre l'ordre dans lequel vous répertoriez les colonnes PARTITION BYavec 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' SELECTinstruction.

Vladimir Baranov
la source