Database
 sql >> Database >  >> RDS >> Database

Stored procedure per ottenere informazioni sulle tabelle del database

In qualità di DBA di SQL Server, ci occupiamo sempre di una delle cose più importanti per l'azienda, i dati. In alcuni casi, le applicazioni possono diventare piuttosto complesse e ti ritroverai con un sacco di tabelle di database sparse per le tue istanze di SQL Server. Ciò potrebbe causare alcuni inconvenienti, come ad esempio:

  • Sapere come si comportano i tuoi dati ogni giorno, in termini di trend di crescita (spazio e/o quantità di righe).
  • Sapere quali tabelle di database richiedono (o richiederanno) una strategia particolare/diversa per archiviare i dati perché crescono troppo velocemente.
  • Sapere quale delle tue tabelle del database occupa troppo spazio, può portare a vincoli di archiviazione.

A causa dell'importanza di questi dettagli, ho creato un paio di stored procedure che possono essere di grande aiuto per qualsiasi DBA di SQL Server che desideri tenere traccia delle informazioni relative alle tabelle del database nel proprio ambiente. Credimi, uno di loro è molto bello.

Considerazioni iniziali

  • Assicurati che l'account che esegue questa stored procedure disponga di privilegi sufficienti. Probabilmente potresti iniziare con sysadmin e poi andare il più granulare possibile per assicurarti che l'utente disponga del minimo dei privilegi richiesti per il corretto funzionamento dell'SP.
  • Gli oggetti del database (tabella del database e stored procedure) verranno creati all'interno del database selezionato al momento dell'esecuzione dello script, quindi scegliere con attenzione.
  • Lo script è realizzato in modo da poter essere eseguito più volte senza ricevere un errore. Per la stored procedure, ho utilizzato l'istruzione "CREATE OR ALTER PROCEDURE", disponibile da SQL Server 2016 SP1. Ecco perché non sorprenderti se non funziona correttamente in una versione precedente.
  • Sentiti libero di cambiare i nomi degli oggetti di database creati.
  • Prestare attenzione ai parametri della Stored Procedure che raccoglie i dati grezzi. Possono essere cruciali in una potente strategia di raccolta dati per visualizzare le tendenze.

Come utilizzare le stored procedure?

  1. Copia e incolla il codice T-SQL (disponibile in questo articolo).
  2. Il primo SP prevede 2 parametri:
    1. @persistData:'Y' se un DBA vuole salvare l'output in una tabella di destinazione e 'N' se il DBA vuole vedere l'output direttamente.
    2. @truncateTable:'Y' per troncare la tabella prima di memorizzare i dati acquisiti e 'N' se i dati correnti vengono mantenuti nella tabella. Tieni presente che il valore di questo parametro è irrilevante se il valore del parametro @persistData è 'N'.
  3. Il secondo SP prevede 1 parametro:
    1. @targetParameter:il nome della colonna da utilizzare per trasporre le informazioni raccolte.

Campi presentati e loro significato

  • nome_database: il nome del database in cui risiede la tabella.
  • schema: il nome dello schema in cui risiede la tabella.
  • nome_tabella: il segnaposto per il nome della tabella.
  • conteggio_righe: il numero di righe attualmente presenti nella tabella.
  • spazio_totale_mb: il numero di MegaByte allocati per la tabella.
  • spazio_usato_mb: il numero di MegaByte effettivamente utilizzati dalla tabella.
  • spazio_inutilizzato_mb: il numero di MegaByte che la tabella non sta utilizzando.
  • data_di creazione: la data/ora di creazione della tabella.
  • data_collection_timestamp: visibile solo se 'Y' viene passato al parametro @persistData. Viene utilizzato per sapere quando l'SP è stato eseguito e le informazioni sono state salvate correttamente nella tabella DBA_Tables.

Test di esecuzione

Dimostrerò alcune esecuzioni delle stored procedure:

/* Visualizza le informazioni sulle tabelle per tutti i database utente */

EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'

/* Mantieni le informazioni delle tabelle del database e interroga la tabella di destinazione, troncando prima la tabella di destinazione */

EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables

Query laterali

*Query per visualizzare le tabelle del database ordinate dal maggior numero di righe al più basso.

SELECT * FROM DBA_Tables ORDER BY row_count DESC;

*Query per visualizzare le tabelle del database ordinate dallo spazio totale più grande al più basso.

SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;

*Query per visualizzare le tabelle del database ordinate dallo spazio utilizzato più grande al più basso.

SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;

*Query per visualizzare le tabelle del database ordinate dal più grande spazio inutilizzato al più basso.

SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;

*Query per visualizzare le tabelle del database ordinate per data di creazione, dalla più recente alla meno recente.

SELECT * FROM DBA_Tables ORDER BY created_date DESC;

Ecco un codice completo della Stored Procedure che acquisisce le informazioni delle tabelle del database:

*All'inizio dello script, vedrai il valore predefinito che la stored procedure assume se non viene passato alcun valore per ciascun parametro.

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

Fino a questo punto, le informazioni sembrano un po' aride, ma permettetemi di cambiare questa percezione con la presentazione di una Stored Procedure complementare. Il suo scopo principale è di trasporre le informazioni raccolte nella tabella di destinazione che funge da fonte per i rapporti sulle tendenze.

Ecco come eseguire la stored procedure:

*A scopo dimostrativo, ho inserito record manuali nella tabella di destinazione denominata t1 per simulare la mia normale esecuzione di stored procedure.

*Il set di risultati è un po' ampio, quindi prenderò un paio di screenshot per mostrare l'output completo.

EXEC TransposeTablesInformation @targetParmeter = 'row_count' 

Principali da asporto

  • Se automatizzi l'esecuzione dello script che popola la tabella di destinazione, puoi immediatamente notare se qualcosa è andato storto con esso o con i tuoi dati. Dai un'occhiata ai dati per la tabella "t1" e la colonna "15". Puoi vedere NULL lì che è stato fatto apposta per mostrarti qualcosa che potrebbe accadere.
  • Con questo tipo di visualizzazione, puoi vedere un comportamento peculiare per le tabelle di database più importanti/critiche.
  • Nell'esempio fornito, ho scelto il campo 'row_count' della tabella di destinazione, ma puoi scegliere qualsiasi altro campo numerico come parametro e ottenere lo stesso formato di tabella, ma con dati diversi.
  • Non preoccuparti, se specifichi un parametro non valido, la Stored Procedure ti avviserà e ne interromperà l'esecuzione.

Ecco un codice completo della Stored Procedure che traspone le informazioni della tabella di destinazione:

*All'inizio dello script, vedrai il valore predefinito che la stored procedure assume se non viene passato alcun valore per ciascun parametro.

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

Conclusione

  • Puoi distribuire l'SP di raccolta dati in ogni istanza di SQL Server supportata e implementare un meccanismo di avviso nell'intero stack di istanze supportate.
  • Se implementi un lavoro di agente che richiede queste informazioni con relativa frequenza, puoi rimanere aggiornato in termini di sapere come si comportano i tuoi dati durante il mese. Naturalmente, puoi andare ancora oltre e archiviare i dati raccolti mensilmente per avere un quadro ancora più ampio; dovresti apportare alcune modifiche al codice, ma ne varrebbe la pena.
  • Assicurati di testare correttamente questo meccanismo in un ambiente sandbox e, quando pianifichi un'implementazione di produzione, assicurati di scegliere periodi di attività bassa.
  • La raccolta di informazioni di questo tipo può aiutare a differenziare un DBA l'uno dall'altro. Probabilmente ci sono 3 strumenti di partito che possono fare la stessa cosa, e anche di più, ma non tutti hanno il budget per permetterselo. Spero che questo possa aiutare chiunque decida di usarlo nel proprio ambiente.