En tant que DBA SQL Server, nous nous occupons toujours de l'une des choses les plus importantes pour l'entreprise, les données. Dans certains cas, les applications peuvent devenir assez complexes et vous vous retrouvez avec une tonne de tables de base de données dispersées autour de votre ou vos instances SQL Server. Cela peut entraîner quelques inconvénients, tels que :
- Connaître le comportement de vos données au quotidien, en termes de tendances de croissance (espace et/ou nombre de lignes).
- Savoir quelles tables de base de données nécessitent (ou nécessiteront) une stratégie particulière/différente pour stocker les données, car elles croissent trop rapidement.
- Savoir lesquelles de vos tables de base de données occupent trop d'espace, ce qui peut entraîner des contraintes de stockage
En raison de l'importance de ces détails, j'ai créé quelques procédures stockées qui peuvent être d'une grande aide pour tout administrateur de base de données SQL Server qui souhaite suivre les informations concernant les tables de base de données dans son environnement. Croyez-moi, l'un d'eux est très cool.
Considérations initiales
- Assurez-vous que le compte exécutant cette procédure stockée dispose de suffisamment de privilèges. Vous pourriez probablement commencer par sysadmin, puis aller aussi précisément que possible pour vous assurer que l'utilisateur dispose du minimum de privilèges requis pour que le SP fonctionne correctement.
- Les objets de la base de données (table de base de données et procédure stockée) seront créés dans la base de données sélectionnée au moment de l'exécution du script, choisissez donc avec soin.
- Le script est conçu de manière à pouvoir être exécuté plusieurs fois sans qu'une erreur ne vous soit renvoyée. Pour la procédure stockée, j'ai utilisé l'instruction "CREATE OR ALTER PROCEDURE", disponible depuis SQL Server 2016 SP1. C'est pourquoi ne soyez pas surpris si cela ne fonctionne pas correctement dans une version antérieure.
- N'hésitez pas à modifier les noms des objets de base de données créés.
- Faites attention aux paramètres de la procédure stockée qui collecte les données brutes. Ils peuvent être cruciaux dans une puissante stratégie de collecte de données pour visualiser les tendances.
Comment utiliser les procédures stockées ?
- Copiez et collez le code T-SQL (disponible dans cet article).
- Le premier SP attend 2 paramètres :
- @persistData :"O" si un DBA souhaite enregistrer la sortie dans une table cible, et "N" si le DBA souhaite voir la sortie directement.
- @truncateTable :'Y' pour tronquer la table avant de stocker les données capturées, et 'N' si les données actuelles sont conservées dans la table. Gardez à l'esprit que la valeur de ce paramètre n'est pas pertinente si la valeur du paramètre @persistData est "N".
- Le deuxième SP attend 1 paramètre :
- @targetParameter :le nom de la colonne à utiliser pour transposer les informations collectées.
Champs présentés et leur signification
- nom_base_données : le nom de la base de données où réside la table.
- schéma : le nom du schéma où réside la table.
- table_name : l'espace réservé pour le nom de la table.
- row_count : le nombre de lignes que la table a actuellement.
- total_space_mb : le nombre de mégaoctets alloués pour la table.
- espace_utilisé_mb : le nombre de mégaoctets réellement utilisés par la table.
- unused_space_mb : le nombre de mégaoctets que la table n'utilise pas.
- created_date : la date/heure de création de la table.
- data_collection_timestamp : visible uniquement si 'Y' est passé au paramètre @persistData. Il est utilisé pour savoir quand le SP a été exécuté et les informations ont été enregistrées avec succès dans la table DBA_Tables.
Tests d'exécution
Je vais démontrer quelques exécutions des procédures stockées :
/* Affiche les informations des tables pour toutes les bases de données utilisateur */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Conserver les informations des tables de la base de données et interroger la table cible, en tronquant d'abord la table cible */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Requêtes annexes
* Requête pour afficher les tables de la base de données triées du plus grand nombre de lignes au plus bas.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
* Requête pour afficher les tables de la base de données triées du plus grand espace total au plus petit.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
* Requête pour afficher les tables de la base de données triées du plus grand espace utilisé au plus petit.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
* Requête pour afficher les tables de la base de données triées du plus grand espace inutilisé au plus petit.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
* Requête pour afficher les tables de la base de données triées par date de création, de la plus récente à la plus ancienne.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Voici un code complet de la procédure stockée qui capture les informations des tables de la base de données :
*Au tout début du script, vous verrez la valeur par défaut que la procédure stockée assume si aucune valeur n'est transmise pour chaque paramètre.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROCEDURE [dbo].[GetTablesData]
@persistData CHAR(1) = 'Y',
@truncateTable CHAR(1) = 'Y'
AS
BEGIN
SET NOCOUNT ON
DECLARE @command NVARCHAR(MAX)
DECLARE @Tmp_TablesInformation TABLE(
[database] [VARCHAR](255) NOT NULL,
[schema] [VARCHAR](64) NOT NULL,
[table] [VARCHAR](255) NOT NULL,
[row_count] [BIGINT]NOT NULL,
[total_space_mb] [DECIMAL](15,2) NOT NULL,
[used_space_mb] [DECIMAL](15,2) NOT NULL,
[unused_space_mb] [DECIMAL](15,2) NOT NULL,
[created_date] [DATETIME] NOT NULL
)
SELECT @command = '
USE [?]
IF DB_ID(''?'') > 4
BEGIN
SELECT
''?'',
s.Name AS [schema],
t.NAME AS [table],
p.rows AS row_count,
CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2) AS DECIMAL(15, 2)) AS total_space_mb,
CAST(ROUND(((SUM(a.used_pages) * 8) / 1024.00), 2) AS DECIMAL(15, 2)) AS used_space_mb,
CAST(ROUND(((SUM(a.total_pages) - SUM(a.used_pages)) * 8) / 1024.00, 2) AS DECIMAL(15, 2)) AS unused_space_mb,
t.create_date as created_date
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE t.NAME NOT LIKE ''dt%''
AND t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
GROUP BY t.Name, s.Name, p.Rows,t.create_date
ORDER BY total_space_mb DESC, t.Name
END'
INSERT INTO @Tmp_TablesInformation
EXEC sp_MSForEachDB @command
IF @persistData = 'N'
SELECT * FROM @Tmp_TablesInformation
ELSE
BEGIN
IF(@truncateTable = 'Y')
TRUNCATE TABLE DBA_Tables
INSERT INTO DBA_Tables
SELECT *,GETDATE() FROM @Tmp_TablesInformation ORDER BY [database],[schema],[table]
END
END
GO
Jusqu'à présent, les informations semblent un peu sèches, mais permettez-moi de changer cette perception avec la présentation d'une procédure stockée complémentaire. Son objectif principal est de transposer les informations collectées dans la table cible qui sert de source aux rapports de tendance.
Voici comment exécuter la procédure stockée :
*À des fins de démonstration, j'ai inséré des enregistrements manuels dans la table cible nommée t1 pour simuler mon exécution habituelle de procédure stockée.
*Le jeu de résultats est un peu large, je vais donc prendre quelques captures d'écran pour montrer le résultat complet.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Contenus clés
- Si vous automatisez l'exécution du script qui remplit la table cible, vous pouvez immédiatement remarquer si quelque chose s'est mal passé avec lui ou avec vos données. Jetez un œil aux données du tableau ‘t1’ et de la colonne ‘15’. Vous pouvez y voir NULL qui a été fait exprès pour vous montrer quelque chose qui pourrait arriver.
- Avec ce type de vue, vous pouvez voir un comportement particulier pour les tables de base de données les plus importantes/critiques.
- Dans l'exemple donné, j'ai choisi le champ "row_count" de la table cible, mais vous pouvez choisir n'importe quel autre champ numérique comme paramètre et obtenir le même format de tableau, mais avec des données différentes.
- Ne vous inquiétez pas, si vous spécifiez un paramètre invalide, la procédure stockée vous avertira et arrêtera son exécution.
Voici un code complet de la procédure stockée qui transpose les informations de la table cible :
*Au tout début du script, vous verrez la valeur par défaut que la procédure stockée assume si aucune valeur n'est transmise pour chaque paramètre.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROCEDURE [dbo].[TransposeTablesInformation]
@targetParameter NVARCHAR(15) = 'row_count'
AS
BEGIN
SET NOCOUNT ON;
IF (@targetParameter <> 'row_count' AND @targetParameter <> 'total_space_mb' AND @targetParameter <> 'used_space_mb' AND @targetParameter <> 'unused_space_mb')
BEGIN
PRINT 'Please specify a valid parameter!'
PRINT 'i.e. row_count | total_space_mb | used_space_mb | unused_space_mb'
RETURN
END
ELSE
BEGIN
CREATE TABLE #TablesInformation(
[database] [VARCHAR](255) NOT NULL,
[schema] [VARCHAR](64) NOT NULL,
[table] [VARCHAR](255) NOT NULL,
[1] [DECIMAL](10,2) NULL,
[2] [DECIMAL](10,2) NULL,
[3] [DECIMAL](10,2) NULL,
[4] [DECIMAL](10,2) NULL,
[5] [DECIMAL](10,2) NULL,
[6] [DECIMAL](10,2) NULL,
[7] [DECIMAL](10,2) NULL,
[8] [DECIMAL](10,2) NULL,
[9] [DECIMAL](10,2) NULL,
[10] [DECIMAL](10,2) NULL,
[11] [DECIMAL](10,2) NULL,
[12] [DECIMAL](10,2) NULL,
[13] [DECIMAL](10,2) NULL,
[14] [DECIMAL](10,2) NULL,
[15] [DECIMAL](10,2) NULL,
[16] [DECIMAL](10,2) NULL,
[17] [DECIMAL](10,2) NULL,
[18] [DECIMAL](10,2) NULL,
[19] [DECIMAL](10,2) NULL,
[20] [DECIMAL](10,2) NULL,
[21] [DECIMAL](10,2) NULL,
[22] [DECIMAL](10,2) NULL,
[23] [DECIMAL](10,2) NULL,
[24] [DECIMAL](10,2) NULL,
[25] [DECIMAL](10,2) NULL,
[26] [DECIMAL](10,2) NULL,
[27] [DECIMAL](10,2) NULL,
[28] [DECIMAL](10,2) NULL,
[29] [DECIMAL](10,2) NULL,
[30] [DECIMAL](10,2) NULL,
[31] [DECIMAL](10,2) NULL
)
INSERT INTO #TablesInformation([database],[schema],[table])
SELECT DISTINCT [database_name],[schema],[table_name]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
DECLARE @databaseName NVARCHAR(255)
DECLARE @schemaName NVARCHAR(64)
DECLARE @tableName NVARCHAR(255)
DECLARE @value DECIMAL(10,2)
DECLARE @dataTimestamp DATETIME
DECLARE @sqlCommand NVARCHAR(MAX)
IF(@targetParameter = 'row_count')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[row_count],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
IF(@targetParameter = 'total_space_mb')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[total_space_mb],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
IF(@targetParameter = 'used_space_mb')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[used_space_mb],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
IF(@targetParameter = 'unused_space_mb')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[unused_space_mb],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
OPEN TablesCursor
FETCH NEXT FROM TablesCursor INTO @databaseName,@schemaName,@tableName,@value,@dataTimestamp
WHILE(@@FETCH_STATUS = 0)
BEGIN
SET @sqlCommand = CONCAT('
UPDATE #TablesInformation
SET [',DAY(@dataTimestamp),'] = ',@value,'
WHERE [database] = ',CHAR(39),@databaseName,CHAR(39),'
AND [schema] = ',CHAR(39),@schemaName+CHAR(39),'
AND [table] = ',CHAR(39),@tableName+CHAR(39),'
')
EXEC(@sqlCommand)
FETCH NEXT FROM TablesCursor INTO @databaseName,@schemaName,@tableName,@value,@dataTimestamp
END
CLOSE TablesCursor
DEALLOCATE TablesCursor
IF(@targetParameter = 'row_count')
SELECT [database],
[schema],
[table],
CONVERT(INT,[1]) AS [1],
CONVERT(INT,[2]) AS [2],
CONVERT(INT,[3]) AS [3],
CONVERT(INT,[4]) AS [4],
CONVERT(INT,[5]) AS [5],
CONVERT(INT,[6]) AS [6],
CONVERT(INT,[7]) AS [7],
CONVERT(INT,[8]) AS [8],
CONVERT(INT,[9]) AS [9],
CONVERT(INT,[10]) AS [10],
CONVERT(INT,[11]) AS [11],
CONVERT(INT,[12]) AS [12],
CONVERT(INT,[13]) AS [13],
CONVERT(INT,[14]) AS [14],
CONVERT(INT,[15]) AS [15],
CONVERT(INT,[16]) AS [16],
CONVERT(INT,[17]) AS [17],
CONVERT(INT,[18]) AS [18],
CONVERT(INT,[19]) AS [19],
CONVERT(INT,[20]) AS [20],
CONVERT(INT,[21]) AS [21],
CONVERT(INT,[22]) AS [22],
CONVERT(INT,[23]) AS [23],
CONVERT(INT,[24]) AS [24],
CONVERT(INT,[25]) AS [25],
CONVERT(INT,[26]) AS [26],
CONVERT(INT,[27]) AS [27],
CONVERT(INT,[28]) AS [28],
CONVERT(INT,[29]) AS [29],
CONVERT(INT,[30]) AS [30],
CONVERT(INT,[31]) AS [31]
FROM #TablesInformation
ELSE
SELECT * FROM #TablesInformation
END
END
GO
Conclusion
- Vous pouvez déployer le SP de collecte de données dans chaque instance SQL Server prise en charge et mettre en œuvre un mécanisme d'alerte sur l'ensemble de votre pile d'instances prises en charge.
- Si vous implémentez une tâche d'agent qui interroge ces informations relativement fréquemment, vous pouvez rester au top pour savoir comment vos données se comportent au cours du mois. Bien sûr, vous pouvez aller encore plus loin et stocker les données collectées mensuellement pour avoir une image encore plus grande; vous devrez apporter quelques modifications au code, mais cela en vaudra vraiment la peine.
- Assurez-vous de tester correctement ce mécanisme dans un environnement sandbox et, lorsque vous planifiez un déploiement en production, veillez à choisir des périodes de faible activité.
- La collecte d'informations de ce type peut aider à différencier un DBA d'un autre. Il existe probablement des outils tiers qui peuvent faire la même chose, voire plus, mais tout le monde n'a pas le budget pour se le permettre. J'espère que cela pourra aider tous ceux qui décident de l'utiliser dans leur environnement.