Som SQL Server DBA'er tager vi os altid af en af de vigtigste ting for virksomheden, nemlig dataene. I nogle tilfælde kan applikationer blive ret komplekse, og du ender med et væld af databasetabeller spredt rundt om dine SQL Server-forekomster. Dette kan medføre nogle få gener, såsom:
- Ved, hvordan dine data opfører sig hver dag med hensyn til væksttendenser (plads og/eller antal rækker).
- Vide, hvad databasetabeller kræver (eller vil kræve) en bestemt/anden strategi for at gemme dataene, fordi de vokser for hurtigt.
- At vide, hvilke af dine databasetabeller der tager for meget plads, hvilket muligvis fører til lagerbegrænsninger.
På grund af vigtigheden af disse detaljer, har jeg lavet et par Stored Procedures, som kan være til stor hjælp for enhver SQL Server DBA, der gerne vil holde styr på information vedrørende databasetabeller i sit miljø. Tro mig, en af dem er meget sej.
Indledende overvejelser
- Sørg for, at kontoen, der udfører denne lagrede procedure, har nok privilegier. Du kunne sandsynligvis starte med sysadmin og derefter gå så detaljeret som muligt for at sikre, at brugeren har det minimum af privilegier, der kræves for at SP'en fungerer korrekt.
- Databaseobjekterne (databasetabel og lagret procedure) vil blive oprettet i den database, der er valgt på det tidspunkt, hvor scriptet udføres, så vælg med omhu.
- Scriptet er lavet på en måde, det kan udføres flere gange uden at få en fejlmeddelelse til dig. Til den lagrede procedure brugte jeg "CREATE OR ALTER PROCEDURE"-sætning, tilgængelig siden SQL Server 2016 SP1. Derfor skal du ikke blive overrasket, hvis det ikke fungerer problemfrit i en tidligere version.
- Du er velkommen til at ændre navnene på de oprettede databaseobjekter.
- Vær opmærksom på parametrene for den lagrede procedure, der indsamler de rå data. De kan være afgørende i en kraftfuld dataindsamlingsstrategi for at visualisere tendenser.
Hvordan bruges de lagrede procedurer?
- Kopiér og indsæt T-SQL-koden (tilgængelig i denne artikel).
- Den første SP forventer 2 parametre:
- @persistData:'Y', hvis en DBA ønsker at gemme outputtet i en måltabel, og 'N', hvis DBA'en ønsker at se outputtet direkte.
- @truncateTable:'Y' for at afkorte tabellen først, før de registrerede data gemmes, og 'N', hvis de aktuelle data bevares i tabellen. Husk, at værdien af denne parameter er irrelevant, hvis værdien af @persistData-parameteren er 'N'.
- Den anden SP forventer 1 parameter:
- @targetParameter:Navnet på den kolonne, der skal bruges til at transponere de indsamlede oplysninger.
Felter præsenteret og deres betydning
- databasenavn: navnet på den database, hvor tabellen ligger.
- skema: navnet på det skema, hvor tabellen ligger.
- tabelnavn: pladsholderen for tabellens navn.
- row_count: antallet af rækker, som tabellen har i øjeblikket.
- total_space_mb: antallet af MegaBytes tildelt til tabellen.
- used_space_mb: antallet af MegaBytes, der faktisk bruges af tabellen.
- unused_space_mb: antallet af MegaBytes, som tabellen ikke bruger.
- created_date: dato/klokkeslæt, hvor tabellen blev oprettet.
- data_indsamling_tidsstempel: kun synlig, hvis 'Y' sendes til parameteren @persistData. Det bruges til at vide, hvornår SP'en blev udført, og oplysningerne blev gemt i DBA_Tables-tabellen.
Udførelsestest
Jeg vil demonstrere et par udførelser af de lagrede procedurer:
/* Vis tabeloplysningerne for alle brugerdatabaser */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Bevar informationen fra databasetabellerne og forespørg måltabellen, afkort måltabellen først */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Sideforespørgsler
*Forespørgsel for at se databasetabellerne sorteret fra det største antal rækker til det laveste.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
*Forespørgsel for at se databasetabellerne sorteret fra den største samlede plads til den laveste.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
*Forespørgsel for at se databasetabellerne sorteret fra den største brugte plads til den laveste.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
*Forespørgsel for at se databasetabellerne sorteret fra den største ubrugte plads til den laveste.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
*Forespørgsel for at se databasetabellerne sorteret efter oprettelsesdato, fra den nyeste til den ældste.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Her er en komplet kode for den lagrede procedure, der fanger oplysningerne fra databasetabellerne:
*I begyndelsen af scriptet vil du se standardværdien, som den lagrede procedure antager, hvis der ikke sendes nogen værdi for hver 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
Indtil nu virker informationen en smule tør, men lad mig ændre denne opfattelse med præsentationen af en supplerende lagret procedure. Dens hovedformål er at transponere de oplysninger, der er indsamlet i måltabellen, der tjener som kilde til trendrapporter.
Sådan kan du udføre den lagrede procedure:
*Til demonstrationsformål har jeg indsat manuelle poster i måltabellen ved navn t1 for at simulere min sædvanlige udførelse af Stored Procedure.
*Resultatsættet er lidt bredt, så jeg vil tage et par skærmbilleder for at vise det fulde output.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Vigtige ting
- Hvis du automatiserer udførelsen af scriptet, der udfylder måltabellen, kan du straks bemærke, om noget gik galt med den eller dine data. Tag et kig på dataene for tabel 't1' og kolonnen '15'. Du kan se NULL der, hvilket blev gjort med vilje for at vise dig noget, der kunne ske.
- Med denne form for visning kan du se en ejendommelig adfærd for de vigtigste/kritiske databasetabeller.
- I det givne eksempel har jeg valgt feltet 'row_count' i måltabellen, men du kan vælge et hvilket som helst andet numerisk felt som parameter og få det samme tabelformat, men med andre data.
- Bare rolig, hvis du angiver en ugyldig parameter, vil den lagrede procedure advare dig og stoppe dens udførelse.
Her er en komplet kode for den lagrede procedure, der transponerer informationen fra måltabellen:
*I begyndelsen af scriptet vil du se standardværdien, som den lagrede procedure antager, hvis der ikke sendes nogen værdi for hver 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
Konklusion
- Du kan implementere dataindsamlings-SP'en i hver SQL Server-instans under din support og implementere en advarselsmekanisme på tværs af hele din stak af understøttede instanser.
- Hvis du implementerer et agentjob, der forespørger om disse oplysninger relativt ofte, kan du være på forkant med spillet med hensyn til at vide, hvordan dine data opfører sig i løbet af måneden. Selvfølgelig kan du gå endnu længere og gemme de månedlige indsamlede data for at få et endnu større billede; du bliver nødt til at lave nogle justeringer af koden, men det ville være det hele værd.
- Sørg for at teste denne mekanisme korrekt i et sandkassemiljø, og når du planlægger en produktionsimplementering, skal du sørge for at vælge perioder med lav aktivitet.
- Indsamling af oplysninger af denne type kan hjælpe med at differentiere en DBA fra hinanden. Der er sikkert 3 parts værktøjer, der kan det samme, og endnu mere, men det er ikke alle, der har råd til det. Jeg håber, at dette kan hjælpe alle, der beslutter sig for at bruge det i deres miljø.