sp_helpindex com mais detalhes

Fala pessoal, tudo bem?
Esse post é principalmente pra vc que costuma atuar em ambientes onde muitos DBA’s já passaram e/ou tem um aspirante a DBA que vive rodando o DTA (database engine tuning advisor) e criando índices hipotéticos ou mantendo índices desabilitados.

Normalmente utiliza-se a sp_help ou sp_helpindex para avaliar características de tabelas e índices no SQL Server, porém essas duas procedures trazem pouquíssimas informações sobre índices.

A sp_help retorna informações da tabela como um todo.

A sp_helpindex que é a procedure de sistema que retorna os dados dos índices conforme abaixo:

Para executar, basta:

EXEC sp_helpindex teste_sp_helpindex_dba

Pensando nisso, com base em scripts que já utilizo, outros que achei na internet e um pouco de criatividade criei a sp_helpindex_dba.

Nela, temos informações de:

  • Colunas chave e colunas incluídas
  • Tipo de índice
  • Compressão
  • Tamanho do índice
  • Fillfactor
  • Status
  • Quantidade de seeks, scan, lookup e escrita
  • Diferença da quantidade entre leitura e escrita
  • Bloqueio de página e linhas
  • entre outras..

Aqui está um exemplo da execução (nesse exemplo não tenho estatísticas de utilização pois criei a tabela apenas para testar) e não tem dados e consultas nela.

Eu utilizei aqui em um novo ambiente que estou atuando e veja como essas informações nos ajudam bastante. (logicamente movimentei algumas colunas do resultado para ocultar algumas informações que possam identificar o cliente.)

Só por aqui já conseguimos ver que tem alguns índices que desde o restart do SQL nunca foram utilizados para consultas.

Então é isso pessoal, segue o script abaixo, testem antes de jogar em um ambiente de produção e se verem algum ajuste legal, me informem nos comentários ou me enviem um email.


USE master
go


create OR ALTER PROCEDURE sp_helpindex_dba
	@objname nvarchar(776)		
as

	set nocount on

	declare @objid int,			
			@indid smallint,	
			@groupid int,  		
			@indname sysname,
			@groupname sysname,
			@status int,
			@keys nvarchar(2126),	
			@inc_columns	nvarchar(max),
			@inc_Count		smallint,
			@loop_inc_Count		smallint,
			@dbname	sysname,
			@ignore_dup_key	bit,
			@is_unique		bit,
			@is_hypothetical	bit,
			@is_primary_key	bit,
			@is_unique_key 	bit,
			@auto_created	bit,
			@no_recompute	bit,
			@filter_definition	nvarchar(max),

			@type_desc VARCHAR(500), 
			@data_compression_desc VARCHAR(500),  
			@IndexSizeGB NUMERIC(12,2), 
			@user_seeks bigint, 
			@last_user_seek datetime, 
			@user_scans BIGINT, 
			@last_user_scan datetime, 
			@user_lookups BIGINT, 
			@Total_User_Escrita BIGINT,  
			@Total_User_Leitura BIGINT, 
			@Dif_Read_Write BIGINT,
			@allow_row_locks BIT, 
			@row_lock_count BIGINT,  
			@row_lock_wait_count BIGINT, 
			@row_lock_wait_in_ms BIGINT, 
			@allow_page_locks BIT, 
			@page_lock_count bigint, 
			@page_lock_wait_count bigint,  
			@page_lock_wait_in_ms bigint, 
			@fill_factor int, 
			@is_padded BIT


	select @dbname = parsename(@objname,3)
	if @dbname is null
		select @dbname = db_name()
	else if @dbname <> db_name()
		begin
			raiserror(15250,-1,-1)
			return (1)
		end

	
	select @objid = object_id(@objname)
	if @objid is NULL
	BEGIN
		raiserror(15009,-1,-1,@objname,@dbname)
		return (1)
	end

	-- Lista índices da tabela
	declare ms_crs_ind cursor local static for
		
		SELECT 
				ix.index_id, ix.data_space_id, ix.name,
				ix.ignore_dup_key, ix.is_unique, ix.is_hypothetical, ix.is_primary_key, ix.is_unique_constraint,
				s.auto_created, s.no_recompute, ix.filter_definition,
				ix.type_desc, t.data_compression_desc, IndexSizeGB, vw.user_seeks, vw.last_user_seek, vw.user_scans, vw.last_user_scan, vw.user_lookups, vw.user_updates as 'Total_User_Escrita',(vw.user_scans + vw.user_seeks + vw.user_lookups) as 'Total_User_Leitura',vw.user_updates - (vw.user_scans + vw.user_seeks + vw.user_lookups) as 'Dif_Read_Write',
				ix.allow_row_locks, vwx.row_lock_count, row_lock_wait_count, row_lock_wait_in_ms,ix.allow_page_locks, vwx.page_lock_count, page_lock_wait_count, page_lock_wait_in_ms, ix.fill_factor, ix.is_padded -- INTO tab_info_index 
		from sys.indexes ix 
		LEFT JOIN sys.dm_db_index_usage_stats vw on ix.index_id = vw.index_id and ix.object_id = vw.object_id AND vw.database_id = DB_ID()
		LEFT JOIN sys.dm_db_index_operational_stats(db_id(), null, NULL, NULL) vwx on vwx.index_id = ix.index_id and ix.object_id = vwx.object_id
		left join (SELECT i.[name] AS IndexName, i.object_id, p.data_compression_desc
		,((SUM(s.[used_page_count]) * 8) /1024 )/1024. AS IndexSizeGB
		FROM sys.dm_db_partition_stats AS s
		INNER JOIN sys.indexes AS i ON s.[object_id] = i.[object_id]    AND s.[index_id] = i.[index_id] 
		inner join sys.partitions p on p.partition_id = s.partition_id
		GROUP BY i.[name],p.data_compression_desc, i.object_id) t on t.IndexName = ix.name AND t.object_id = ix.object_id
		inner JOIN sys.stats s on ix.object_id = s.object_id and ix.index_id = s.stats_id
		where ix.object_id = @objid 

	OPEN ms_crs_ind
	FETCH ms_crs_ind INTO @indid, @groupid, @indname, @ignore_dup_key, @is_unique, @is_hypothetical,
			@is_primary_key, @is_unique_key, @auto_created, @no_recompute, @filter_definition, 
			@type_desc, @data_compression_desc, @IndexSizeGB, @user_seeks, @last_user_seek, @user_scans, @last_user_scan, @user_lookups, @Total_User_Escrita,  @Total_User_Leitura, @Dif_Read_Write,
			@allow_row_locks, @row_lock_count, @row_lock_wait_count, @row_lock_wait_in_ms, @allow_page_locks, @page_lock_count, @page_lock_wait_count, @page_lock_wait_in_ms, @fill_factor, @is_padded

			PRINT @indid

	
	if @@fetch_status < 0
	begin
		deallocate ms_crs_ind
		raiserror(15472,-1,-1,@objname) 
		return (0)
	end

	CREATE TABLE #tabind
	(
		index_name				sysname	collate database_default NOT NULL,
		index_id				int,
		ignore_dup_key			BIT,
		is_unique				BIT,
		is_hypothetical			BIT,
		is_primary_key			BIT,
		is_unique_key			BIT,
		auto_created			bit,
		no_recompute			BIT,
		groupname				sysname collate database_default NULL,
		index_keys				NVARCHAR(2126)	collate database_default NOT NULL, -- see @keys above for length descr
		filter_definition		NVARCHAR(max),
		inc_Count				smallint,
		inc_columns				NVARCHAR(max),
		[type_desc]				[NVARCHAR](60) NULL,
		[data_compression_desc] [nvarchar](60) NULL,
		[IndexSizeGB]			[NUMERIC](25, 6) NULL,
		[user_seeks]			[BIGINT] NULL,
		[last_user_seek]		[DATETIME] NULL,
		[user_scans]			[BIGINT] NULL,
		[last_user_scan]		[DATETIME] NULL,
		[user_lookups]			[BIGINT] NULL,
		[Total_User_Escrita]	[bigint] NULL,
		[Total_User_Leitura]	[bigint] NULL,
		[Dif_Read_Write]		[BIGINT] NULL,
		[allow_row_locks]		[BIT] NULL,
		[row_lock_count]		[BIGINT] NULL,
		[row_lock_wait_count]	[bigint] NULL,
		[row_lock_wait_in_ms]	[BIGINT] NULL,
		[allow_page_locks]		[BIT] NULL,
		[page_lock_count]		[BIGINT] NULL,
		[page_lock_wait_count]	[BIGINT] NULL,
		[page_lock_wait_in_ms]	[BIGINT] NULL,
		[fill_factor]			[TINYINT] NOT NULL,
		[is_padded]				[BIT] NULL
	)



	CREATE TABLE #IncludedColumns
	(	RowNumber	smallint,
		[Name]	nvarchar(128)
	)

	while @@fetch_status >= 0
	begin
		declare @i int, @thiskey nvarchar(131) 

		select @keys = index_col(@objname, @indid, 1), @i = 2
		if (indexkey_property(@objid, @indid, 1, 'isdescending') = 1)
			select @keys = @keys  + '(-)'

		select @thiskey = index_col(@objname, @indid, @i)
		if ((@thiskey is not null) and (indexkey_property(@objid, @indid, @i, 'isdescending') = 1))
			select @thiskey = @thiskey + '(-)'

		while (@thiskey is not null )
		begin
			select @keys = @keys + ', ' + @thiskey, @i = @i + 1
			select @thiskey = index_col(@objname, @indid, @i)
			if ((@thiskey is not null) and (indexkey_property(@objid, @indid, @i, 'isdescending') = 1))
				select @thiskey = @thiskey + '(-)'
		end

		
		SELECT @inc_Count = count(*)
		FROM
		sys.tables AS tbl
		INNER JOIN sys.indexes AS si 
			ON (si.index_id > 0 
				and si.is_hypothetical = 0) 
				AND (si.object_id=tbl.object_id)
		INNER JOIN sys.index_columns AS ic 
			ON (ic.column_id > 0 
				and (ic.key_ordinal > 0 or ic.partition_ordinal = 0 or ic.is_included_column != 0)) 
				AND (ic.index_id=CAST(si.index_id AS int) AND ic.object_id=si.object_id)
		INNER JOIN sys.columns AS clmns 
			ON clmns.object_id = ic.object_id 
			and clmns.column_id = ic.column_id
		WHERE ic.is_included_column = 1 and
			(si.index_id = @indid) and 
			(tbl.object_id= @objid)

		IF @inc_Count > 0
		BEGIN
			
			DELETE FROM #IncludedColumns
			INSERT #IncludedColumns
				SELECT ROW_NUMBER() OVER (ORDER BY clmns.column_id) 
				, clmns.name 
			FROM
			sys.tables AS tbl
			INNER JOIN sys.indexes AS si 
				ON (si.index_id > 0 
					and si.is_hypothetical = 0) 
					AND (si.object_id=tbl.object_id)
			INNER JOIN sys.index_columns AS ic 
				ON (ic.column_id > 0 
					and (ic.key_ordinal > 0 or ic.partition_ordinal = 0 or ic.is_included_column != 0)) 
					AND (ic.index_id=CAST(si.index_id AS int) AND ic.object_id=si.object_id)
			INNER JOIN sys.columns AS clmns 
				ON clmns.object_id = ic.object_id 
				and clmns.column_id = ic.column_id
			WHERE ic.is_included_column = 1 and
				(si.index_id = @indid) and 
				(tbl.object_id= @objid)
			
			SELECT @inc_columns = [Name] FROM #IncludedColumns WHERE RowNumber = 1

			SET @loop_inc_Count = 1

			WHILE @loop_inc_Count < @inc_Count
			BEGIN
				SELECT @inc_columns = @inc_columns + ', ' + [Name] 
					FROM #IncludedColumns WHERE RowNumber = @loop_inc_Count + 1
				SET @loop_inc_Count = @loop_inc_Count + 1
			END
		END
		-- SELECT @indname, @inc_columns
		select @groupname = null
		select @groupname = name from sys.data_spaces where data_space_id = @groupid


		insert into #tabind values (@indname, @indid, @ignore_dup_key, @is_unique, @is_hypothetical,
			@is_primary_key, @is_unique_key, @auto_created, @no_recompute, @groupname, @keys, @filter_definition, @inc_Count, @inc_columns,@type_desc, @data_compression_desc, @IndexSizeGB, @user_seeks, @last_user_seek, @user_scans, @last_user_scan, @user_lookups, @Total_User_Escrita,  @Total_User_Leitura, @Dif_Read_Write,
			@allow_row_locks, @row_lock_count, @row_lock_wait_count, @row_lock_wait_in_ms, @allow_page_locks, @page_lock_count, @page_lock_wait_count, @page_lock_wait_in_ms, @fill_factor, @is_padded)
	
		SELECT @inc_columns = null
		
		-- Next index
		fetch ms_crs_ind into @indid, @groupid, @indname, @ignore_dup_key, @is_unique, @is_hypothetical,
			@is_primary_key, @is_unique_key, @auto_created, @no_recompute, @filter_definition,
			@type_desc, @data_compression_desc, @IndexSizeGB, @user_seeks, @last_user_seek, @user_scans, @last_user_scan, @user_lookups, @Total_User_Escrita,  @Total_User_Leitura, @Dif_Read_Write,
			@allow_row_locks, @row_lock_count, @row_lock_wait_count, @row_lock_wait_in_ms, @allow_page_locks, @page_lock_count, @page_lock_wait_count, @page_lock_wait_in_ms, @fill_factor, @is_padded
	end
	deallocate ms_crs_ind

	select
		index_name				
		,index_id				
		,ignore_dup_key			
		,is_unique				
		,is_hypothetical			
		,is_primary_key			
		,is_unique_key			
		,auto_created			
		,no_recompute			
		,groupname				
		,index_keys				
		,filter_definition		
		,inc_Count				
		,inc_columns				
		,[type_desc]				
		,[data_compression_desc] 
		,[IndexSizeGB]			
		,[user_seeks]			
		,[last_user_seek]		
		,[user_scans]			
		,[last_user_scan]		
		,[user_lookups]			
		,[Total_User_Escrita]	
		,[Total_User_Leitura]	
		,[Dif_Read_Write]		
		,[allow_row_locks]		
		,[row_lock_count]		
		,[row_lock_wait_count]	
		,[row_lock_wait_in_ms]	
		,[allow_page_locks]		
		,[page_lock_count]		
		,[page_lock_wait_count]	
		,[page_lock_wait_in_ms]	
		,[fill_factor]			
		,[is_padded]				
	from #tabind
	order by index_id

	return (0)

GO

-- seta a procedure como procedure de sistema para ser usada em qualquer vd
exec sys.sp_MS_marksystemobject 'sp_helpindex_dba'

Para utilizar vc pode executar assim;

exec sp_helpindex_dba nome_da_tabela

Ou você pode facilitar sua e criar um atalho de teclado para executá-la apenas marcando a tabela e clicando de acordo com o atalho que vc configurar. Eu coloquei no Ctrl + F1.

Para alterar o atalho basta acessar o SSMS > tools > options > environment > keyboard > query Shortcuts e digitar comando em stored procedure.

Por hoje é isso pessoal, espero que tenham gostado e possam curtir o post.

Abraços

Deixe um comentário