Als SQL Server DBA's zorgen wij altijd voor een van de belangrijkste zaken voor de business, de data. In sommige gevallen kunnen applicaties behoorlijk complex worden en krijg je uiteindelijk een heleboel databasetabellen verspreid over je SQL Server-instantie(s). Dit kan tot enkele ongemakken leiden, zoals:
- Weten hoe uw gegevens zich elke dag gedragen, in termen van groeitrends (ruimte en/of aantal rijen).
- Weten welke databasetabellen een bepaalde/andere strategie nodig hebben (of zullen vereisen) om de gegevens op te slaan omdat deze te snel groeien.
- Weten welke van uw databasetabellen te veel ruimte in beslag nemen, wat mogelijk tot opslagbeperkingen leidt.
Vanwege het belang van deze details, heb ik een aantal opgeslagen procedures gemaakt die van grote hulp kunnen zijn voor elke SQL Server DBA die informatie over databasetabellen in zijn/haar omgeving wil bijhouden. Geloof me, een van hen is erg cool.
Eerste overwegingen
- Zorg ervoor dat het account dat deze Opgeslagen Procedure uitvoert voldoende rechten heeft. U kunt waarschijnlijk beginnen met sysadmin en vervolgens zo gedetailleerd mogelijk gaan om ervoor te zorgen dat de gebruiker de minimale rechten heeft die nodig zijn om de SP goed te laten werken.
- De database-objecten (databasetabel en opgeslagen procedure) worden gemaakt in de database die is geselecteerd op het moment dat het script wordt uitgevoerd, dus kies zorgvuldig.
- Het script is zo gemaakt dat het meerdere keren kan worden uitgevoerd zonder dat u een fout krijgt. Voor de opgeslagen procedure heb ik de instructie "CREATE OR ALTER PROCEDURE" gebruikt, beschikbaar sinds SQL Server 2016 SP1. Wees daarom niet verbaasd als het in een eerdere versie niet soepel werkt.
- Voel je vrij om de namen van de gemaakte database-objecten te wijzigen.
- Let op de parameters van de opgeslagen procedure die de onbewerkte gegevens verzamelt. Ze kunnen cruciaal zijn in een krachtige strategie voor het verzamelen van gegevens om trends te visualiseren.
Hoe de opgeslagen procedures gebruiken?
- Kopieer en plak de T-SQL-code (beschikbaar in dit artikel).
- De eerste SP verwacht 2 parameters:
- @persistData:'Y' als een DBA de uitvoer in een doeltabel wil opslaan, en 'N' als de DBA de uitvoer direct wil zien.
- @truncateTable:'Y' om de tabel eerst af te kappen voordat de vastgelegde gegevens worden opgeslagen, en 'N' als de huidige gegevens in de tabel worden bewaard. Houd er rekening mee dat de waarde van deze parameter niet relevant is als de waarde van de @persistData-parameter 'N' is.
- De tweede SP verwacht 1 parameter:
- @targetParameter:de naam van de kolom die moet worden gebruikt om de verzamelde informatie te transponeren.
Gepresenteerde velden en hun betekenis
- database_name: de naam van de database waarin de tabel zich bevindt.
- schema: de naam van het schema waar de tabel zich bevindt.
- tabelnaam: de tijdelijke aanduiding voor de naam van de tafel.
- row_count: het aantal rijen dat de tabel momenteel heeft.
- total_space_mb: het aantal MegaBytes dat aan de tafel is toegewezen.
- used_space_mb: het aantal MegaBytes dat daadwerkelijk door de tafel wordt gebruikt.
- unused_space_mb: het aantal MegaBytes dat de tabel niet gebruikt.
- created_date: de datum/tijd waarop de tabel is gemaakt.
- data_collection_timestamp: alleen zichtbaar als 'Y' wordt doorgegeven aan de parameter @persistData. Het wordt gebruikt om te weten wanneer de SP is uitgevoerd en de informatie met succes is opgeslagen in de tabel DBA_Tables.
Uitvoeringstests
Ik zal een paar uitvoeringen van de opgeslagen procedures demonstreren:
/* Toon de tabelinformatie voor alle gebruikersdatabases */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Houd de informatie van de databasetabellen aan en bevraag de doeltabel, waarbij u eerst de doeltabel afkapt */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Sidequery's
*Query om de databasetabellen te bekijken, gesorteerd van het grootste aantal rijen naar het laagste.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
*Query om de databasetabellen te bekijken, gesorteerd van de grootste totale ruimte naar de laagste.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
*Query om de databasetabellen te bekijken, gesorteerd van de grootste naar de laagste gebruikte ruimte.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
*Query om de databasetabellen te bekijken, gesorteerd van de grootste ongebruikte ruimte naar de laagste.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
*Query om de databasetabellen te bekijken, gesorteerd op aanmaakdatum, van de nieuwste tot de oudste.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Hier is een volledige code van de opgeslagen procedure die de informatie van de databasetabellen vastlegt:
*Aan het begin van het script ziet u de standaardwaarde die de Opgeslagen Procedure aanneemt als er geen waarde wordt doorgegeven voor elke parameter.
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
Tot nu toe lijkt de informatie een beetje droog, maar laat me die perceptie veranderen met de presentatie van een aanvullende Stored Procedure. Het belangrijkste doel is om de verzamelde informatie in de doeltabel om te zetten die als bron dient voor trendrapporten.
Zo kunt u de Opgeslagen Procedure uitvoeren:
*Voor demonstratiedoeleinden heb ik handmatige records ingevoegd in de doeltabel met de naam t1 om mijn gebruikelijke uitvoering van opgeslagen procedures te simuleren.
*De resultatenset is een beetje breed, dus ik zal een paar screenshots maken om de volledige uitvoer te laten zien.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Belangrijkste afhaalrestaurants
- Als u de uitvoering van het script dat de doeltabel vult, automatiseert, kunt u onmiddellijk merken of er iets mis is gegaan met het script of met uw gegevens. Bekijk de gegevens voor tabel 't1' en de kolom '15'. Je kunt daar NULL zien, wat met opzet is gedaan om je iets te laten zien dat zou kunnen gebeuren.
- Met dit soort weergave kunt u een eigenaardig gedrag zien voor de belangrijkste/kritieke databasetabellen.
- In het gegeven voorbeeld heb ik het veld 'row_count' van de doeltabel gekozen, maar je kunt elk ander numeriek veld als parameter kiezen en hetzelfde tabelformaat krijgen, maar met andere gegevens.
- Maak je geen zorgen, als je een ongeldige parameter opgeeft, zal de Opgeslagen Procedure je waarschuwen en de uitvoering stoppen.
Hier is een volledige code van de opgeslagen procedure die de informatie van de doeltabel transponeert:
*Aan het begin van het script ziet u de standaardwaarde die de Opgeslagen Procedure aanneemt als er geen waarde wordt doorgegeven voor elke parameter.
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
Conclusie
- U kunt de SP voor gegevensverzameling implementeren in elke SQL Server-instantie onder uw ondersteuning en een waarschuwingsmechanisme implementeren voor uw hele stapel ondersteunde instanties.
- Als u een agenttaak implementeert die deze informatie relatief vaak opvraagt, kunt u de vinger aan de pols houden door te weten hoe uw gegevens zich gedurende de maand gedragen. U kunt natuurlijk nog verder gaan en de maandelijks verzamelde gegevens opslaan om een nog groter geheel te krijgen; je zou wat aanpassingen aan de code moeten maken, maar het zou absoluut de moeite waard zijn.
- Zorg ervoor dat u dit mechanisme goed test in een sandbox-omgeving en, wanneer u een productie-implementatie plant, zorg ervoor dat u perioden van lage activiteit kiest.
- Het verzamelen van informatie van dit type kan helpen om een DBA van elkaar te onderscheiden. Er zijn waarschijnlijk tools van derden die hetzelfde kunnen doen, en zelfs meer, maar niet iedereen heeft het budget om het te betalen. Ik hoop dat dit iedereen kan helpen die besluit het in zijn omgeving te gebruiken.