Database
 sql >> Base de données >  >> RDS >> Database

Performances de sys.partitions

sys.partitions semble être une UNION ALL de deux ensembles de résultats (magasin de lignes et magasin de colonnes) et la plupart de mes requêtes aboutissent à deux analyses de sysrowsets. Existe-t-il un filtre que je peux mettre sur une requête de sys.partitions si je sais que la ligne que je recherche est rowstore ?

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 et TABLE ALUCOUNT objets une fois. Mais ni vous ni moi ne pouvons faire grand-chose à ce sujet pour le moment.

La colonne cmprlevel vient de sys.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 tout OUTER 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 et ALUCOUNT , et il y en a plusieurs qui apparaissent dans la liste, mais seulement deux sont prometteurs :sys.internal_partitions et sys.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 de sys.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 les s dans internals 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ù.