Je travaille sur une solution de maintenance personnalisée à l'aide de la sys.dm_db_index_physical_stats
vue. Je l'ai actuellement référencé à partir d'une procédure stockée. Maintenant, lorsque cette procédure stockée s'exécute sur l'une de mes bases de données, elle fait ce que je veux qu'elle fasse et extrait une liste de tous les enregistrements concernant n'importe quelle base de données. Lorsque je le place sur une autre base de données, il extrait une liste de tous les enregistrements relatifs à cette base de données uniquement.
Par exemple (code en bas):
- L'exécution de la requête sur la base de données 6 affiche les informations [demandées] pour les bases de données 1-10.
- L'exécution de la requête sur la base de données 3 affiche les informations [demandées] pour la base de données 3 uniquement.
La raison pour laquelle je veux cette procédure spécifiquement sur la base de données trois est parce que je préfère garder tous les objets de maintenance dans la même base de données. J'aimerais que ce travail soit dans la base de données de maintenance et fonctionne comme s'il se trouvait dans cette base de données d'application.
Code:
ALTER PROCEDURE [dbo].[GetFragStats]
@databaseName NVARCHAR(64) = NULL
,@tableName NVARCHAR(64) = NULL
,@indexID INT = NULL
,@partNumber INT = NULL
,@Mode NVARCHAR(64) = 'DETAILED'
AS
BEGIN
SET NOCOUNT ON;
DECLARE @databaseID INT, @tableID INT
IF @databaseName IS NOT NULL
AND @databaseName NOT IN ('tempdb','ReportServerTempDB')
BEGIN
SET @databaseID = DB_ID(@databaseName)
END
IF @tableName IS NOT NULL
BEGIN
SET @tableID = OBJECT_ID(@tableName)
END
SELECT D.name AS DatabaseName,
T.name AS TableName,
I.name AS IndexName,
S.index_id AS IndexID,
S.avg_fragmentation_in_percent AS PercentFragment,
S.fragment_count AS TotalFrags,
S.avg_fragment_size_in_pages AS PagesPerFrag,
S.page_count AS NumPages,
S.index_type_desc AS IndexType
FROM sys.dm_db_index_physical_stats(@databaseID, @tableID,
@indexID, @partNumber, @Mode) AS S
JOIN
sys.databases AS D ON S.database_id = D.database_id
JOIN
sys.tables AS T ON S.object_id = T.object_id
JOIN
sys.indexes AS I ON S.object_id = I.object_id
AND S.index_id = I.index_id
WHERE
S.avg_fragmentation_in_percent > 10
ORDER BY
DatabaseName, TableName, IndexName, PercentFragment DESC
END
GO
la source
Réponses:
Une façon serait de créer une procédure système dans
master
, puis de créer un wrapper dans votre base de données de maintenance. Notez que cela ne fonctionnera que pour une base de données à la fois.Tout d'abord, en master:
Maintenant, dans votre base de données de maintenance, créez un wrapper qui utilise SQL dynamique pour définir correctement le contexte:
(La raison pour laquelle le nom de la base de données ne peut pas vraiment être
NULL
parce que vous ne pouvez pas vous associer à des choses commesys.objects
etsys.indexes
puisqu'elles existent indépendamment dans chaque base de données. Donc peut-être avoir une procédure différente si vous voulez des informations à l'échelle de l'instance.)Vous pouvez maintenant l'appeler pour n'importe quelle autre base de données, par exemple
Et vous pouvez toujours créer un
synonym
dans chaque base de données afin que vous n'ayez même pas à référencer le nom de la base de données de maintenance:Une autre façon serait d'utiliser SQL dynamique, mais cela ne fonctionnera que pour une base de données à la fois:
Encore une autre façon serait de créer une vue (ou une fonction table) pour réunir les noms de table et d'index de toutes vos bases de données, mais vous devrez coder en dur les noms de base de données dans la vue et les conserver pendant que vous ajoutez / supprimer les bases de données que vous souhaitez autoriser à inclure dans cette requête. Contrairement aux autres, cela vous permettrait de récupérer les statistiques de plusieurs bases de données à la fois.
Tout d'abord, la vue:
Puis la procédure:
la source
Eh bien, il y a de mauvaises nouvelles, de bonnes nouvelles avec un hic et quelques très bonnes nouvelles.
Les mauvaises nouvelles
Les objets T-SQL s'exécutent dans la base de données où ils résident. Il y a deux exceptions (pas très utiles):
sp_
et qui existent dans la[master]
base de données (pas une excellente option: une base de données à la fois, en ajoutant quelque chose[master]
, éventuellement en ajoutant des synonymes à chaque base de données, ce qui doit être fait pour chaque nouvelle base de données)sp_
proc stocké[master]
.La bonne nouvelle (avec un hic)
Beaucoup (peut-être la plupart?) Connaissent les fonctions intégrées pour obtenir des métadonnées vraiment communes:
L'utilisation de ces fonctions peut éliminer le besoin pour les JOIN
sys.databases
(bien que celui-ci ne soit pas vraiment un problème),sys.objects
(préféré àsys.tables
ce qui exclut les vues indexées) etsys.schemas
(vous manquiez celui-ci, et tout n'est pas dans ledbo
schéma ;-). Mais même en supprimant trois des quatre JOIN, nous sommes toujours fonctionnellement au même endroit, non? Faux-o!L'une des caractéristiques intéressantes des fonctions
OBJECT_NAME()
etOBJECT_SCHEMA_NAME()
est qu'elles ont un deuxième paramètre facultatif pour@database_id
. Cela signifie que, même si JOINDRE à ces tables (à l'exception desys.databases
) est spécifique à la base de données, l'utilisation de ces fonctions vous permet d'obtenir des informations à l'échelle du serveur. Même OBJECT_ID () permet des informations à l'échelle du serveur en lui donnant un nom d'objet complet.En incorporant ces fonctions de métadonnées dans la requête principale, nous pouvons simplifier tout en élargissant au-delà de la base de données actuelle. La première passe de refactorisation de la requête nous donne:
Et maintenant pour le "catch": il n'y a pas de fonction de métadonnées pour obtenir les noms d'index, et encore moins un nom à l'échelle du serveur. Alors, c'est ça? Sommes-nous à 90% terminés et toujours bloqués devant être dans une base de données particulière pour obtenir des
sys.indexes
données? Avons-nous vraiment besoin de créer une procédure stockée pour utiliser Dynamic SQL pour remplir, chaque fois que notre proc principal s'exécute, une table temporaire de toutes lessys.indexes
entrées dans toutes les bases de données afin que nous puissions nous y joindre? NON!La très bonne nouvelle
Ainsi vient une petite fonctionnalité que certaines personnes aiment détester, mais lorsqu'elle est utilisée correctement, elle peut faire des choses incroyables. Oui: SQLCLR. Pourquoi? Parce que les fonctions SQLCLR peuvent évidemment soumettre des instructions SQL, mais par la nature même de la soumission à partir du code d'application, il s'agit de Dynamic SQL. Contrairement aux fonctions T-SQL, les fonctions SQLCLR peuvent injecter un nom de base de données dans la requête avant de l'exécuter. Ce qui signifie, nous pouvons créer notre propre fonction pour refléter la capacité
OBJECT_NAME()
etOBJECT_SCHEMA_NAME()
de prendredatabase_id
et obtenir les informations de cette base de données.Le code suivant est cette fonction. Mais il faut un nom de base de données au lieu d'un ID pour qu'il n'a pas besoin de faire l'étape supplémentaire de la recherche (ce qui le rend un peu moins compliqué et un peu plus rapide).
Si vous le constatez, nous utilisons la connexion contextuelle, qui est non seulement rapide, mais fonctionne également dans les
SAFE
assemblages. Oui, cela fonctionne dans une assemblée marquée commeSAFE
, donc il (ou des variantes de celui-ci) devrait même fonctionner sur Azure SQL Database V12(la prise en charge de SQLCLR a été supprimée, plutôt brutalement, d'Azure SQL Database en avril 2016) .Notre refactorisation de deuxième passe de la requête principale nous donne donc les informations suivantes:
C'est ça! Cette UDF scalaire SQLCLR et votre procédure stockée T-SQL de maintenance peuvent vivre dans la même
[maintenance]
base de données centralisée . ET, vous n'avez pas besoin de traiter une base de données à la fois; vous disposez maintenant de fonctions de métadonnées pour toutes les informations dépendantes qui concernent tout le serveur.PS Il n'y a pas de
.IsNull
vérification des paramètres d'entrée dans le code C # car l'objet wrapper T-SQL doit être créé avec l'WITH RETURNS NULL ON NULL INPUT
option:Notes complémentaires:
La méthode décrite ici peut également être utilisée pour résoudre d'autres problèmes très similaires de fonctions de métadonnées inter-bases de données manquantes. La suggestion Microsoft Connect suivante est un exemple d'un tel cas. Et, voyant que Microsoft l'a fermé comme "Won't Fix", il est clair qu'ils ne sont pas intéressés à fournir des fonctions intégrées comme
OBJECT_NAME()
pour répondre à ce besoin (d'où la solution de contournement publiée sur cette suggestion :-).Ajouter une fonction de métadonnées pour obtenir le nom de l'objet à partir de hobt_id
Pour en savoir plus sur l'utilisation de SQLCLR, veuillez consulter la série Stairway to SQLCLR que j'écris sur SQL Server Central (une inscription gratuite est requise; désolé, je ne contrôle pas les politiques de ce site).
La
IndexName()
fonction SQLCLR illustrée ci-dessus est disponible, précompilée, dans un script facile à installer sur Pastebin. Le script active la fonction "Intégration CLR" si elle n'est pas déjà activée et l'assembly est marqué commeSAFE
. Il est compilé avec .NET Framework version 2.0 afin qu'il fonctionne dans SQL Server 2005 et plus récent (c'est-à-dire toutes les versions qui prennent en charge SQLCLR).Fonction de métadonnées SQLCLR pour la base de données croisée IndexName ()
Si quelqu'un s'intéresse à la
IndexName()
fonction SQLCLR et à plus de 320 autres fonctions et procédures stockées, elle est disponible dans la bibliothèque SQL # (dont je suis l'auteur). Veuillez noter que bien qu'il existe une version gratuite, la fonction Sys_IndexName est uniquement disponible dans la version complète (avec une fonction Sys_AssemblyName similaire ).la source