Cette question a été postée sur #sqlhelp par Jake Manske, et elle a été portée à mon attention par Erik Darling.
Je ne me souviens pas avoir jamais eu de problème de performances avec sys.partitions
. Ma première pensée (reprise par Joey D'Antoni) était qu'un filtre sur la data_compression
colonne devrait évitez l'analyse redondante et réduisez le temps d'exécution des requêtes de moitié environ. Cependant, ce prédicat n'est pas poussé vers le bas, et la raison pour laquelle prend un peu de déballage.
Pourquoi sys.partitions est-il lent ?
Si vous regardez la définition de sys.partitions
, c'est essentiellement ce que Jake a décrit - un UNION ALL
de toutes les partitions columnstore et rowstore, avec TROIS références explicites à sys.sysrowsets
(source abrégée ici):
CREATE VIEW sys.partitions AS WITH partitions_columnstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs OUTER APPLY OpenRowset(TABLE ALUCOUNT, rs .rowsetid, 0, 0, 0) ct-------- *** ^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE .. . sysconv(bit, rs.status &0x00010000) =1 -- Considérer uniquement les index de base columnstore ), partitions_rowstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs -------- *** ^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE ... sysconv(bit, rs .status &0x00010000) =0 - Ignorer les index de base du magasin de colonnes et les lignes orphelines. ) SELECT ...cols... from partitions_rowstore p OUTER APPLY OpenRowset(TABLE ALUCOUNT, p.partition_id, 0, 0, p.object_id) ct union all SELECT ...cols... FROM partitions_columnstore as P1 LEFT JOIN (SELECT ...cols... FROM sys.sysrowsets rs OUTER APP LY OpenRowset(TABLE ALUCOUNT, rs.rowsetid, 0, 0, 0) ct------- *** ^^^^^^^^^^^^^^ *** ) ...Ce point de vue semble bricolé, probablement en raison de problèmes de compatibilité descendante. Il pourrait sûrement être réécrit pour être plus efficace, en particulier pour ne référencer que les
sys.sysrowsets
etTABLE ALUCOUNT
objets une fois. Mais ni vous ni moi ne pouvons faire grand-chose à ce sujet pour le moment.La colonne
cmprlevel
vient desys.sysrowsets
(un préfixe d'alias sur la référence de colonne aurait été utile). Vous espéreriez qu'un prédicat contre une colonne se produirait logiquement avant toutOUTER APPLY
et pourrait empêcher l'un des scans, mais ce n'est pas ce qui se passe. Exécution de la requête simple suivante :SELECT * FROM sys.partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0 ;Génère le plan suivant lorsqu'il y a des index columnstore dans les bases de données (cliquez pour agrandir) :
Plan pour sys.partitions, avec les index columnstore présents
Et le plan suivant quand il n'y en a pas (cliquez pour agrandir) :
Plan pour sys.partitions, sans index columnstore présent
Il s'agit du même plan estimé, mais SentryOne Plan Explorer est capable de mettre en évidence lorsqu'une opération est ignorée lors de l'exécution. Cela se produit pour la troisième analyse dans ce dernier cas, mais je ne sais pas s'il existe un moyen de réduire davantage ce nombre d'analyses d'exécution. la deuxième analyse se produit même lorsque la requête ne renvoie aucune ligne.
Dans le cas de Jake, il a beaucoup d'objets, donc effectuer cette analyse même deux fois est perceptible, douloureux et une fois de trop. Et très honnêtement je ne sais pas si
TABLE ALUCOUNT
, un appel de bouclage interne et non documenté, doit également analyser plusieurs fois certains de ces objets plus volumineux.En regardant la source, je me suis demandé s'il y avait un autre prédicat qui pourrait être transmis à la vue qui pourrait contraindre la forme du plan, mais je ne pense vraiment pas qu'il y ait quoi que ce soit qui puisse avoir un impact.
Une autre vue fonctionnera-t-elle ?
Nous pourrions, cependant, essayer une vue complètement différente. J'ai cherché d'autres vues contenant des références aux deux
sys.sysrowsets
etALUCOUNT
, et il y en a plusieurs qui apparaissent dans la liste, mais seulement deux sont prometteurs :sys.internal_partitions
etsys.system_internals_partitions
.sys.internal_partitions
J'ai essayé
sys.internal_partitions
d'abord :SELECT * FROM sys.internal_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0 ;Mais le plan n'était pas beaucoup mieux (cliquez pour agrandir) :
Planifier pour sys.internal_partitions
Il n'y a que deux scans contre
sys.sysrowsets
cette fois, mais les analyses ne sont de toute façon pas pertinentes car la requête est loin de produire les lignes qui nous intéressent. Nous ne voyons que les lignes pour les objets liés au columnstore (comme l'indique la documentation).sys.system_internals_partitions
Essayons
sys.system_internals_partitions
. Je suis un peu méfiant à ce sujet, car ce n'est pas pris en charge (voir l'avertissement ici), mais patientez un instant :SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0 ;Dans la base de données avec les index columnstore, il y a une analyse contre
sys.sysschobjs
, mais maintenant seulement un analyse par rapport àsys.sysrowsets
(cliquez pour agrandir) :Plan pour sys.system_internals_partitions, avec les index columnstore présents
Si nous exécutons la même requête dans la base de données sans index columnstore, le plan est encore plus simple, avec une recherche sur
sys.sysschobjs
(cliquez pour agrandir) :Planifier pour sys.system_internals_partitions, sans index columnstore présent
Cependant, ce n'est pas tout à fait ce que nous recherchons, ou du moins pas tout à fait ce que Jake recherchait, car cela inclut également les artefacts des index columnstore. Si nous ajoutons ces filtres, le résultat réel correspond désormais à notre requête précédente, beaucoup plus coûteuse :
SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0 AND p.is_columnstore =0 AND p.is_orphaned =0 ;En prime, le scan contre
sys.sysschobjs
est devenu une recherche même dans la base de données avec des objets columnstore. La plupart d'entre nous ne remarqueront pas cette différence, mais si vous êtes dans un scénario comme celui de Jake, vous pourriez (cliquez pour agrandir) :Plan plus simple pour sys.system_internals_partitions, avec des filtres supplémentaires
sys.system_internals_partitions
expose un ensemble de colonnes différent desys.partitions
(certains sont complètement différents, d'autres ont de nouveaux noms) donc, si vous consommez la sortie en aval, vous devrez vous adapter à ceux-ci. Vous voudrez également valider qu'il renvoie toutes les informations souhaitées sur les index rowstore, optimisés en mémoire et columnstore, et n'oubliez pas ces tas embêtants. Et, enfin, soyez prêt à omettre less
dansinternals
plusieurs, plusieurs fois.Conclusion
Comme je l'ai mentionné ci-dessus, cette vue système n'est pas officiellement prise en charge, sa fonctionnalité peut donc changer à tout moment. il peut également être déplacé sous la connexion administrateur dédiée (DAC) ou complètement supprimé du produit. N'hésitez pas à utiliser cette approche si
sys.partitions
ne fonctionne pas bien pour vous, mais assurez-vous d'avoir un plan de secours. Et assurez-vous qu'il est documenté comme quelque chose que vous testez de régression lorsque vous commencez à tester les futures versions de SQL Server, ou après la mise à niveau, juste au cas où.