J'ai une colonne calculée persistante sur une table qui est simplement constituée de colonnes concaténées, par exemple
CREATE TABLE dbo.T
(
ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
A VARCHAR(20) NOT NULL,
B VARCHAR(20) NOT NULL,
C VARCHAR(20) NOT NULL,
D DATE NULL,
E VARCHAR(20) NULL,
Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL
);
Dans ce Comp
n'est pas unique, et D est la date de début valide de chaque combinaison de A, B, C
, donc j'utilise la requête suivante pour obtenir la date de fin pour chacun A, B, C
(essentiellement la prochaine date de début pour la même valeur de Comp):
SELECT t1.ID,
t1.Comp,
t1.D,
D2 = ( SELECT TOP 1 t2.D
FROM dbo.T t2
WHERE t2.Comp = t1.Comp
AND t2.D > t1.D
ORDER BY t2.D
)
FROM dbo.T t1
WHERE t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;
J'ai ensuite ajouté un index à la colonne calculée pour aider à cette requête (et aussi à d'autres):
CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;
Le plan de requête m'a cependant surpris. J'aurais pensé que puisque j'ai une clause where qui l'indique D IS NOT NULL
et que je trie par Comp
, et que je ne fais référence à aucune colonne en dehors de l'index, l'index sur la colonne calculée pourrait être utilisé pour analyser t1 et t2, mais j'ai vu un index clusterisé balayage.
J'ai donc forcé l'utilisation de cet indice pour voir s'il donnait un meilleur plan:
SELECT t1.ID,
t1.Comp,
t1.D,
D2 = ( SELECT TOP 1 t2.D
FROM dbo.T t2
WHERE t2.Comp = t1.Comp
AND t2.D > t1.D
ORDER BY t2.D
)
FROM dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE t1.D IS NOT NULL
ORDER BY t1.Comp;
Qui a donné ce plan
Cela montre qu'une recherche de clé est utilisée, dont les détails sont les suivants:
Maintenant, selon la documentation de SQL-Server:
Vous pouvez créer un index sur une colonne calculée définie avec une expression déterministe mais imprécise si la colonne est marquée PERSISTED dans l'instruction CREATE TABLE ou ALTER TABLE. Cela signifie que le moteur de base de données stocke les valeurs calculées dans la table et les met à jour lorsque toute autre colonne dont dépend la colonne calculée est mise à jour. Le moteur de base de données utilise ces valeurs persistantes lorsqu'il crée un index sur la colonne et lorsque l'index est référencé dans une requête. Cette option vous permet de créer un index sur une colonne calculée lorsque le moteur de base de données ne peut pas prouver avec précision si une fonction qui renvoie des expressions de colonne calculées, en particulier une fonction CLR créée dans le .NET Framework, est à la fois déterministe et précise.
Donc, si, comme le disent les documents, "le moteur de base de données stocke les valeurs calculées dans la table" et que la valeur est également stockée dans mon index, pourquoi une recherche de clé est-elle requise pour obtenir A, B et C quand elles ne sont pas référencées dans la requête du tout? Je suppose qu'ils sont utilisés pour calculer Comp, mais pourquoi? De plus, pourquoi la requête peut-elle utiliser l'index sur t2
, mais pas sur t1
?
Requêtes et DDL sur SQL Fiddle
NB J'ai marqué SQL Server 2008 car c'est la version sur laquelle se trouve mon problème principal, mais j'ai également le même comportement en 2012.
FOJNtoLSJNandLASJN
) qui se traduit par des choses qui ne fonctionnent pas comme nous l'espérons, et qui laisse des ordures (BaseRow / Checksums) qui sont utiles dans certains types de plans (par exemple les curseurs) mais pas nécessaires ici.Chk
est la somme de contrôle! Merci, je n'en étais pas sûr. À l'origine, je pensais que cela pourrait être lié aux contraintes de vérification.Bien que cela puisse être un peu une co-incidence en raison de la nature artificielle de vos données de test, comme vous l'avez mentionné SQL 2012, j'ai essayé une réécriture:
Cela a donné un bon plan à faible coût en utilisant votre index et avec des lectures beaucoup plus faibles que les autres options (et les mêmes résultats pour vos données de test).
Je soupçonne que vos données réelles sont plus compliquées, il peut donc y avoir certains scénarios où cette requête se comporte sémantiquement différemment de la vôtre, mais cela montre parfois que les nouvelles fonctionnalités peuvent faire une réelle différence.
J'ai fait des expériences avec des données plus variées et j'ai trouvé des scénarios pour correspondre et d'autres non:
la source
comp
n'est pas une colonne calculée, vous ne voyez pas le tri.LEAD
fonction a fonctionné exactement comme je le souhaiterais sur mon instance locale de 2012 express. Malheureusement, cet inconvénient mineur pour moi n'a pas encore été considéré comme une raison suffisante pour mettre à niveau les serveurs de production ...Lorsque j'ai essayé d'effectuer les mêmes actions, j'ai obtenu les autres résultats. Tout d'abord, mon plan d'exécution pour une table sans index se présente comme suit:
Comme nous pouvons le voir à partir de l'analyse d'index clusterisé (t2), le prédicat est utilisé pour déterminer les lignes nécessaires à renvoyer (en raison de la condition):
Lorsque l'index a été ajouté, peu importe qu'il ait été défini par l'opérateur WITH ou non, le plan d'exécution est devenu le suivant:
Comme nous pouvons le voir, l'analyse d'index en cluster est remplacée par l'analyse d'index. Comme nous l'avons vu ci-dessus, SQL Server utilise les colonnes source de la colonne calculée pour effectuer la correspondance de la requête imbriquée. Pendant l'analyse d'index clusterisé, toutes ces valeurs peuvent être acquises en même temps (aucune opération supplémentaire n'est requise). Lorsque l'index a été ajouté, le filtrage des lignes nécessaires de la table (dans la sélection principale) s'effectue en fonction de l'index, mais les valeurs des colonnes source pour la colonne calculée
comp
doivent encore être obtenues (dernière opération Nested Loop) .Pour cette raison, l'opération de recherche de clé est utilisée - pour obtenir les données des colonnes source de celle calculée.
PS ressemble à un bogue dans SQL Server.
la source