Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Automatizando a desfragmentação de índice no banco de dados MS SQL Server

Prefácio


A World Wide Web oferece várias informações sobre desfragmentação de índice do SQL Server ou reconstrução de índice do SQL Server. No entanto, a maioria das recomendações se refere a bancos de dados que possuem tempo mínimo de carregamento (principalmente, à noite).

E os bancos de dados usados ​​para modificação de dados e recuperação de informações 24 horas por dia, 7 dias por semana?

Neste artigo, fornecerei um mecanismo para automatizar a desfragmentação de índices do SQL Server implementado em um banco de dados usado na empresa em que trabalho. Esse mecanismo permite desfragmentar os índices necessários regularmente, pois a fragmentação do índice ocorre constantemente no sistema 24 horas por dia, 7 dias por semana. Muitas vezes, isso não é suficiente para realizar a desfragmentação do índice uma vez por dia.

Solução


Em primeiro lugar, vamos dar uma olhada na abordagem geral:
  1. Criando uma visualização mostrando quais índices foram fragmentados e a porcentagem dos índices fragmentados.
  2. Criando uma tabela para armazenar os resultados da desfragmentação do índice.
  3. Criando um procedimento armazenado para analisar e desfragmentar o índice selecionado.
  4. Criando uma visualização para visualizar as estatísticas dos resultados da desfragmentação do índice.
  5. Criando uma tarefa no Agente para executar o procedimento armazenado implementado.

E agora, vamos dar uma olhada na implementação:

1. Criando uma visualização mostrando quais índices foram fragmentados e a porcentagem dos índices fragmentados:
USE [Database_Name]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE view [srv].[vIndexDefrag]
as
with info as 
(SELECT
	[object_id],
	database_id,
	index_id,
	index_type_desc,
	index_level,
	fragment_count,
	avg_fragmentation_in_percent,
	avg_fragment_size_in_pages,
	page_count,
	record_count,
	ghost_record_count
	FROM sys.dm_db_index_physical_stats
    (DB_ID(N'Database_Name')
	, NULL, NULL, NULL ,
	N'DETAILED')
	where index_level = 0
	)
SELECT
	b.name as db,
	s.name as shema,
	t.name as tb,
	i.index_id as idx,
	i.database_id,
	idx.name as index_name,
	i.index_type_desc,i.index_level as [level],
	i.[object_id],
	i.fragment_count as frag_num,
	round(i.avg_fragmentation_in_percent,2) as frag,
	round(i.avg_fragment_size_in_pages,2) as frag_page,
	i.page_count as [page],
	i.record_count as rec,
	i.ghost_record_count as ghost,
	round(i.avg_fragmentation_in_percent*i.page_count,0) as func
FROM Info as i
inner join [sys].[databases]	as b	on i.database_id = b.database_id
inner join [sys].[all_objects]	as t	on i.object_id = t.object_id
inner join [sys].[schemas]	as s	on t.[schema_id] = s.[schema_id]
inner join [sys].[indexes]	as idx on t.object_id = idx.object_id and idx.index_id = i.index_id
 where i.avg_fragmentation_in_percent >= 30 and i.index_type_desc <> 'HEAP';
GO

Esta visualização mostra apenas índices com porcentagem de fragmentação maior que 30, ou seja, índices que requerem desfragmentação. Ele mostra apenas os índices que não são heaps, pois estes últimos podem levar a efeitos negativos, como bloqueio de tal heap ou fragmentação adicional do índice.

A exibição usa a importante exibição do sistema sys.dm_db_index_physical_stats.

2. Criando uma tabela para armazenar os resultados da desfragmentação do índice:
USE [Database_Name]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [srv].[Defrag](
	[ID] [bigint] IDENTITY(794,1) NOT NULL,
	[db] [nvarchar](100) NULL,
	[shema] [nvarchar](100) NULL,
	[table] [nvarchar](100) NULL,
	[IndexName] [nvarchar](100) NULL,
	[frag_num] [int] NULL,
	[frag] [decimal](6, 2) NULL,
	[page] [int] NULL,
	[rec] [int] NULL,
        [func] [int] NULL,
	[ts] [datetime] NULL,
	[tf] [datetime] NULL,
	[frag_after] [decimal](6, 2) NULL,
	[object_id] [int] NULL,
	[idx] [int] NULL,
	[InsertUTCDate] [datetime] NOT NULL,
 CONSTRAINT [PK_Defrag] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
GO

ALTER TABLE [srv].[Defrag] ADD  CONSTRAINT [DF_Defrag_InsertUTCDate]  DEFAULT (getutcdate()) FOR [InsertUTCDate];
GO

A coisa mais importante sobre esta tabela é manter em mente a exclusão de dados (por exemplo, dados com mais de 1 mês).

Os campos da tabela se tornarão compreensíveis a partir do próximo ponto.

3. Criando um procedimento armazenado para analisar e desfragmentar o índice selecionado:
USE [Database_Name]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [srv].[AutoDefragIndex]
AS
BEGIN
	SET NOCOUNT ON;

	--declaring required variables
	declare @IndexName nvarchar(100) --index name
	,@db nvarchar(100)			 --database name
	,@Shema nvarchar(100)			 --schema name
	,@Table nvarchar(100)			 --table name
	,@SQL_Str nvarchar (2000)		 --string for command generation
	,@frag decimal(6,2)				 --fragmentation percentage before defragmentation
	,@frag_after decimal(6,2)		 --fragmentation percentage after defragmentation
        --Number of fragments at the final level of the IN_ROW_DATA allocation unit
        ,@frag_num int				 
	,@func int					 --round(i.avg_fragmentation_in_percent*i.page_count,0)
	,@page int					 --number of index pages  
	,@rec int						 --total number of records
	,@ts datetime					 --date and time of defragmentation start
	,@tf datetime					 --date and time of defragmenation finish
	--Table or view object ID for which the index was created
        ,@object_id int					 
	,@idx int;						 --index ID

	--getting current date and time
	set @ts = getdate();
	
	--getting next index for defragmenation
	--Here the important index is selected. At that, a situation when one index is defragmented regularly, while other indexes are not selected for defragmentation is unlikely.
	select top 1
		@IndexName = index_name,
		@db=db,
		@Shema = shema,
		@Table = tb,
		@frag = frag,
		@frag_num = frag_num,
		@func=func,
		@page =[page],
		@rec = rec,
		@object_id = [object_id],
		@idx = idx 
	from  [srv].[vIndexDefrag]
	order by func*power((1.0-
	  convert(float,(select count(*) from SRV.[srv].[Defrag] vid where vid.db=db 
														 and vid.shema = shema
														 and vid.[table] = tb
														 and vid.IndexName = index_name))
	 /
	 convert(float,
                  case  when (exists (select top 1 1 from SRV.[srv].[Defrag] vid1 where vid1.db=db))
                            then (select count(*) from  SRV.[srv].[Defrag] vid1 where vid1.db=db)
                            else 1.0 end))
                    ,3) desc

	--if we get such index
	if(@db is not null)
	begin
	   --index reorganization
	   set @SQL_Str = 'alter index ['[email protected]+'] on ['[email protected]+'].['[email protected]+'] Reorganize';

		execute sp_executesql  @SQL_Str;

		--getting current date and time
		set @tf = getdate()

		--getting fragmentation percentage after defragmentation
		SELECT @frag_after = avg_fragmentation_in_percent
		FROM sys.dm_db_index_physical_stats
			(DB_ID(@db), @object_id, @idx, NULL ,
			N'DETAILED')
		where index_level = 0;

		--writing the result of work
		insert into SRV.srv.Defrag(
									[db],
									[shema],
									[table],
									[IndexName],
									[frag_num],
									[frag],
									[page],
									[rec],
									ts,
									tf,
									frag_after,
									object_id,
									idx
								  )
						select
									@db,
									@shema,
									@table,
									@IndexName,
									@frag_num,
									@frag,
									@page,
									@rec,
									@ts,
									@tf,
									@frag_after,
									@object_id,
									@idx;
		
		--upating statistics for index
		set @SQL_Str = 'UPDATE STATISTICS ['[email protected]+'].['[email protected]+'] ['[email protected]+']';

		execute sp_executesql  @SQL_Str;
	end
END

4. Criando uma visualização para visualizar as estatísticas dos resultados da desfragmentação do índice:
USE [Database_Name]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE view [srv].[vStatisticDefrag] as
SELECT top 1000
	  [db]
	  ,[shema]
          ,[table]
          ,[IndexName]
          ,avg([frag]) as AvgFrag
          ,avg([frag_after]) as AvgFragAfter
	  ,avg(page) as AvgPage
  FROM [srv].[Defrag]
  group by [db], [shema], [table], [IndexName]
  order by abs(avg([frag])-avg([frag_after])) desc;
GO

Essa exibição pode ser usada para notificar os administradores diariamente sobre os resultados da automação da desfragmentação do índice.

5. Criando uma tarefa no Agente para executar o procedimento armazenado implementado

Aqui, precisamos escolher o tempo de forma experimental. No meu caso, em algum lugar eu tenho 5 minutos, em algum lugar – 1 hora.

Este algoritmo pode ser expandido em vários bancos de dados, mas neste caso, precisamos de um ponto adicional 6:

Reunindo todas as estatísticas da automação de desfragmentação de índices em um só lugar para posterior envio aos administradores.

E agora, gostaria de me debruçar sobre as recomendações já fornecidas para suporte de índice:
  1. A desfragmentação simultânea de todos os índices durante a carga mínima do banco de dados é inaceitável para os sistemas 24 horas por dia, 7 dias por semana, pois os índices são constantemente fragmentados e quase não há tempo em que o banco de dados permaneça ocioso.
  2. Reorganização de índice do SQL Server – esta operação bloqueia uma tabela ou partição (no caso de um índice particionado), o que não é bom para os sistemas 24 horas por dia, 7 dias por semana. Em seguida, a reconstrução do índice no modo em tempo real é suportada apenas na solução Enterprise e também pode causar danos aos dados.

Esse método não é o ideal, mas pode lidar com sucesso garantindo que os índices sejam desfragmentados adequadamente (não excedendo 30-40% de fragmentação) para seu uso subsequente pelo otimizador para criar planos de execução.

Ficarei grato por seus comentários com prós e contras fundamentados dessa abordagem, bem como pelas sugestões alternativas testadas.

Referências

  • Reorganizar e reconstruir índices
  • sys.dm_db_index_physical_stats




Ferramenta útil:


dbForge Index Manager – suplemento SSMS útil para analisar o status de índices SQL e corrigir problemas com fragmentação de índice.