Som SQL Server DBA:er tar vi alltid hand om en av de viktigaste sakerna för verksamheten, data. I vissa fall kan applikationer bli ganska komplexa och du slutar med massor av databastabeller utspridda runt dina SQL Server-instanser. Detta kan leda till några olägenheter, till exempel:
- Att veta hur din data beter sig varje dag, när det gäller tillväxttrender (utrymme och/eller antal rader).
- Att veta vad databastabeller kräver (eller kommer att kräva) en viss/annan strategi för att lagra data eftersom den växer för snabbt.
- Att veta vilka av dina databastabeller som tar för mycket utrymme, vilket kan leda till lagringsbegränsningar.
På grund av vikten av dessa detaljer har jag skapat ett par lagrade procedurer som kan vara till stor hjälp för alla SQL Server DBA som vill hålla reda på information om databastabeller i sin miljö. Tro mig, en av dem är väldigt cool.
Inledande överväganden
- Se till att kontot som kör denna lagrade procedur har tillräckligt med privilegier. Du kan förmodligen börja med sysadmin och sedan gå så detaljerat som möjligt för att se till att användaren har de minsta rättigheter som krävs för att SP ska fungera korrekt.
- Databasobjekten (databastabell och lagrad procedur) kommer att skapas inuti den databas som valts när skriptet körs, så välj noga.
- Skriptet är skapat på ett sätt som kan köras flera gånger utan att få ett felmeddelande till dig. För den lagrade proceduren använde jag "CREATE OR ALTER PROCEDURE"-satsen, tillgänglig sedan SQL Server 2016 SP1. Bli därför inte förvånad om det inte fungerar smidigt i en tidigare version.
- Ändra gärna namnen på de skapade databasobjekten.
- Var uppmärksam på parametrarna för den lagrade proceduren som samlar in rådata. De kan vara avgörande i en kraftfull datainsamlingsstrategi för att visualisera trender.
Hur använder man de lagrade procedurerna?
- Kopiera och klistra in T-SQL-koden (tillgänglig i den här artikeln).
- Den första SP förväntar sig två parametrar:
- @persistData:'Y' om en DBA vill spara utdata i en måltabell, och 'N' om DBA vill se utdata direkt.
- @truncateTable:'Y' för att trunkera tabellen först innan du lagrar insamlad data, och 'N' om den aktuella informationen hålls i tabellen. Tänk på att värdet på denna parameter är irrelevant om värdet på @persistData-parametern är 'N'.
- Den andra SP förväntar sig 1 parameter:
- @targetParameter:Namnet på kolumnen som ska användas för att överföra den insamlade informationen.
Fält som presenteras och deras betydelse
- databasnamn: namnet på databasen där tabellen finns.
- schema: namnet på schemat där tabellen finns.
- tabellnamn: platshållaren för tabellens namn.
- row_count: antalet rader som tabellen för närvarande har.
- total_space_mb: antalet MegaByte som tilldelats för tabellen.
- used_space_mb: antalet megabyte som faktiskt används av tabellen.
- unused_space_mb: antalet megabyte som tabellen inte använder.
- skapat_datum: datum/tid då tabellen skapades.
- data_insamling_tidsstämpel: synlig endast om 'Y' skickas till parametern @persistData. Den används för att veta när SP kördes och informationen sparades i tabellen DBA_Tables.
Utförandetester
Jag kommer att demonstrera några exekveringar av de lagrade procedurerna:
/* Visa tabellinformation för alla användardatabaser */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Behåll informationen från databastabellerna och fråga efter måltabellen, trunkera måltabellen först */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Sidofrågor
*Fråga för att se databastabellerna sorterade från det största antalet rader till det lägsta.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
*Fråga för att se databastabellerna sorterade från det största totala utrymmet till det lägsta.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
*Fråga för att se databastabellerna sorterade från det största använda utrymmet till det lägsta.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
*Fråga för att se databastabellerna sorterade från det största oanvända utrymmet till det lägsta.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
*Fråga för att se databastabellerna sorterade efter skapandedatum, från de senaste till de äldsta.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Här är en komplett kod för den lagrade proceduren som fångar informationen från databastabellerna:
*I början av skriptet kommer du att se standardvärdet som den lagrade proceduren antar om inget värde skickas för varje 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
Fram till denna punkt verkar informationen lite torr, men låt mig ändra den uppfattningen med presentationen av en kompletterande lagrad procedur. Dess huvudsakliga syfte är att överföra informationen som samlas in i måltabellen som fungerar som en källa för trendrapporter.
Så här kan du köra den lagrade proceduren:
*I demonstrationssyfte har jag infogat manuella poster i måltabellen med namnet t1 för att simulera min vanliga körning av lagrad procedur.
*Resultatuppsättningen är lite bred, så jag tar ett par skärmdumpar för att visa hela resultatet.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Nyckelalternativ
- Om du automatiserar exekveringen av skriptet som fyller måltabellen kan du omedelbart märka om något gick fel med det eller med dina data. Ta en titt på data för tabellen "t1" och kolumnen "15". Du kan se NULL där som gjordes med avsikt för att visa dig något som kan hända.
- Med den här typen av vy kan du se ett märkligt beteende för de viktigaste/kritiska databastabellerna.
- I det givna exemplet har jag valt fältet 'row_count' i måltabellen, men du kan välja vilket annat numeriskt fält som helst som parameter och få samma tabellformat, men med andra data.
- Oroa dig inte, om du anger en ogiltig parameter kommer den lagrade proceduren att varna dig och stoppa dess exekvering.
Här är en komplett kod för den lagrade proceduren som överför informationen från måltabellen:
*I början av skriptet kommer du att se standardvärdet som den lagrade proceduren antar om inget värde skickas för varje 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
Slutsats
- Du kan distribuera datainsamlings-SP i varje SQL Server-instans under ditt stöd och implementera en varningsmekanism över hela din stapel med instanser som stöds.
- Om du implementerar ett agentjobb som frågar efter denna information relativt ofta, kan du hålla koll på spelet när det gäller att veta hur din data beter sig under månaden. Naturligtvis kan du gå ännu längre och lagra den månatliga insamlade datan för att få en ännu större bild; du måste göra några justeringar av koden, men det skulle vara helt värt det.
- Se till att testa den här mekanismen ordentligt i en sandlådemiljö och, när du planerar för en produktionsinstallation, se till att välja perioder med låg aktivitet.
- Att samla in information av den här typen kan hjälpa till att skilja en DBA från varandra. Det finns säkert 3-partsverktyg som kan göra samma sak, och till och med fler, men alla har inte budgeten att ha råd med. Jag hoppas att detta kan hjälpa alla som bestämmer sig för att använda det i sin miljö.