sql >> Databasteknik >  >> RDS >> Database

Lagrad procedur för att få information om databastabeller

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?

  1. Kopiera och klistra in T-SQL-koden (tillgänglig i den här artikeln).
  2. Den första SP förväntar sig två parametrar:
    1. @persistData:'Y' om en DBA vill spara utdata i en måltabell, och 'N' om DBA vill se utdata direkt.
    2. @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'.
  3. Den andra SP förväntar sig 1 parameter:
    1. @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ö.

  1. Hur man använder FILEPROPERTY() i SQL Server

  2. Hur man använder samma schema för flera SQL Server-agentjobb (T-SQL)

  3. Vad är SYSNAME-datatyp i SQL Server?

  4. Begränsa PostgreSQL-användaråtkomst med hjälp av schema och vyer