Database
 sql >> Base de Dados >  >> RDS >> Database

Procedimento armazenado para obter informações de tabelas de banco de dados


Como DBAs SQL Server, sempre cuidamos de uma das coisas mais importantes para o negócio, os dados. Em alguns casos, os aplicativos podem ficar bastante complexos e você acaba com uma tonelada de tabelas de banco de dados espalhadas pelas instâncias do SQL Server. Isso pode levar a alguns inconvenientes, como:
  • Saber como seus dados se comportam todos os dias, em termos de tendências de crescimento (espaço e/ou quantidade de linhas).
  • Saber quais tabelas de banco de dados exigem (ou exigirão) uma estratégia específica/diferente para armazenar os dados porque estão crescendo muito rápido.
  • Saber quais das suas tabelas de banco de dados ocupam muito espaço, possivelmente levando a restrições de armazenamento.

Devido à importância desses detalhes, criei alguns Stored Procedures que podem ser de grande ajuda para qualquer DBA SQL Server que queira acompanhar informações sobre tabelas de banco de dados em seu ambiente. Confie em mim, um deles é muito legal.

Considerações iniciais

  • Certifique-se de que a conta que executa este procedimento armazenado tenha privilégios suficientes. Você provavelmente poderia começar com sysadmin e, em seguida, ir o mais granular possível para garantir que o usuário tenha o mínimo de privilégios necessários para que o SP funcione corretamente.
  • Os objetos do banco de dados (tabela do banco de dados e procedimento armazenado) serão criados dentro do banco de dados selecionado no momento da execução do script, portanto, escolha com cuidado.
  • O script é criado de forma que possa ser executado várias vezes sem que você receba um erro. Para o Stored Procedure, usei a instrução “CREATE OR ALTER PROCEDURE”, disponível desde o SQL Server 2016 SP1. É por isso que não se surpreenda se não funcionar sem problemas em uma versão anterior.
  • Sinta-se à vontade para alterar os nomes dos objetos de banco de dados criados.
  • Preste atenção aos parâmetros do procedimento armazenado que coleta os dados brutos. Eles podem ser cruciais em uma estratégia poderosa de coleta de dados para visualizar tendências.

Como usar os procedimentos armazenados?

  1. Copie e cole o código T-SQL (disponível neste artigo).
  2. O primeiro SP espera 2 parâmetros:
    1. @persistData:'Y' se um DBA quiser salvar a saída em uma tabela de destino e 'N' se o DBA quiser ver a saída diretamente.
    2. @truncateTable:'Y' para truncar a tabela antes de armazenar os dados capturados e 'N' se os dados atuais forem mantidos na tabela. Lembre-se de que o valor desse parâmetro é irrelevante se o valor do parâmetro @persistData for 'N'.
  3. O segundo SP espera 1 parâmetro:
    1. @targetParameter:o nome da coluna a ser usada para transpor as informações coletadas.

Campos apresentados e seu significado

  • database_name: o nome do banco de dados onde a tabela reside.
  • esquema: o nome do esquema onde a tabela reside.
  • table_name: o espaço reservado para o nome da tabela.
  • row_count: o número de linhas que a tabela tem atualmente.
  • total_space_mb: o número de MegaBytes alocados para a tabela.
  • used_space_mb: o número de MegaBytes realmente em uso pela tabela.
  • unused_space_mb: o número de MegaBytes que a tabela não está usando.
  • data_criada: a data/hora em que a tabela foi criada.
  • data_collection_timestamp: visível apenas se ‘Y’ for passado para o parâmetro @persistData. Ele é usado para saber quando o SP foi executado e as informações foram salvas com sucesso na tabela DBA_Tables.

Testes de execução


Vou demonstrar algumas execuções dos Stored Procedures:

/* Exibe as informações das tabelas para todos os bancos de dados do usuário */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'

/* Persiste as informações das tabelas do banco de dados e consulta a tabela de destino, truncando primeiro a tabela de destino */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables

Consultas secundárias


*Consulta para visualizar as tabelas do banco de dados classificadas do maior número de linhas para o menor.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;

*Consulta para visualizar as tabelas do banco de dados classificadas do maior espaço total para o menor.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;

*Consulta para visualizar as tabelas do banco de dados classificadas do maior espaço usado para o menor.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;

*Consulta para visualizar as tabelas do banco de dados classificadas do maior espaço não utilizado para o menor.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;

*Consulta para visualizar as tabelas do banco de dados ordenadas por data de criação, da mais recente à mais antiga.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;

Segue um código completo do Stored Procedure que captura as informações das tabelas do banco de dados:

*No início do script, você verá o valor padrão que o procedimento armazenado assume se nenhum valor for passado para cada parâmetro.
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

Até este ponto, as informações parecem um pouco secas, mas deixe-me mudar essa percepção com a apresentação de um Stored Procedure complementar. Seu principal objetivo é transpor as informações coletadas na tabela de destino que serve de fonte para relatórios de tendências.

Veja como você pode executar o procedimento armazenado:

*Para fins de demonstração, inseri registros manuais na tabela de destino chamada t1 para simular minha execução usual de procedimento armazenado.

*O conjunto de resultados é um pouco amplo, então farei algumas capturas de tela para mostrar o resultado completo.
EXEC TransposeTablesInformation @targetParmeter = 'row_count' 

Principais conclusões

  • Se você automatizar a execução do script que preenche a tabela de destino, poderá perceber imediatamente se algo deu errado com ele ou com seus dados. Dê uma olhada nos dados para a tabela 't1' e a coluna '15'. Você pode ver NULL lá, que foi feito de propósito para mostrar algo que pode acontecer.
  • Com esse tipo de visualização, você pode ver um comportamento peculiar para as tabelas de banco de dados mais importantes/críticas.
  • No exemplo dado, escolhi o campo 'row_count' da tabela de destino, mas você pode escolher qualquer outro campo numérico como parâmetro e obter o mesmo formato de tabela, mas com dados diferentes.
  • Não se preocupe, se você especificar um parâmetro inválido, o procedimento armazenado o avisará e interromperá sua execução.

Segue um código completo do Stored Procedure que transpõe as informações da tabela de destino:

*No início do script, você verá o valor padrão que o procedimento armazenado assume se nenhum valor for passado para cada parâmetro.
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

Conclusão

  • Você pode implantar o SP de coleta de dados em todas as instâncias do SQL Server sob seu suporte e implementar um mecanismo de alerta em toda a pilha de instâncias com suporte.
  • Se você implementar um trabalho de agente que consulte essas informações com relativa frequência, poderá ficar por dentro do jogo em termos de saber como seus dados se comportam durante o mês. Claro, você pode ir ainda mais longe e armazenar os dados coletados mensalmente para ter uma visão ainda maior; você teria que fazer alguns ajustes no código, mas valeria a pena.
  • Certifique-se de testar esse mecanismo corretamente em um ambiente de sandbox e, ao planejar uma implantação de produção, certifique-se de escolher períodos de baixa atividade.
  • A coleta de informações desse tipo pode ajudar a diferenciar um DBA um do outro. Provavelmente existem ferramentas de terceiros que podem fazer a mesma coisa e até mais, mas nem todos têm orçamento para pagar. Espero que isso possa ajudar qualquer pessoa que decida usá-lo em seu ambiente.