sql >> Base de Datos >  >> RDS >> Sqlserver

Automatización de la desfragmentación de índices en la base de datos de MS SQL Server

Prefacio

La World Wide Web ofrece mucha información sobre la desfragmentación de índices de SQL Server o la reconstrucción de índices de SQL Server. Sin embargo, la mayoría de las recomendaciones se refieren a bases de datos que tienen un tiempo de carga mínimo (principalmente, por la noche).

¿Y qué pasa con las bases de datos que se utilizan tanto para la modificación de datos como para la recuperación de información las 24 horas del día, los 7 días de la semana?

En este artículo, proporcionaré un mecanismo para automatizar la desfragmentación de índices de SQL Server implementado en una base de datos utilizada en la empresa para la que trabajo. Este mecanismo permite desfragmentar los índices requeridos de manera regular ya que la fragmentación de índices ocurre constantemente en el sistema 24/7. A menudo, esto no es suficiente para realizar la desfragmentación del índice una vez al día.

Solución

En primer lugar, echemos un vistazo al enfoque general:

  1. Crear una vista que muestre qué índices se han fragmentado y el porcentaje de índices fragmentados.
  2. Crear una tabla para almacenar los resultados de la desfragmentación del índice.
  3. Crear un procedimiento almacenado para analizar y desfragmentar el índice seleccionado.
  4. Crear una vista para ver las estadísticas de los resultados de la desfragmentación del índice.
  5. Crear una tarea en el Agente para ejecutar el procedimiento almacenado implementado.

Y ahora, echemos un vistazo a la implementación:

1. Crear una vista que muestre qué índices se han fragmentado y el porcentaje de í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 vista muestra solo los índices con un porcentaje de fragmentación superior a 30, es decir, índices que requieren desfragmentación. Solo muestra índices que no son montones, ya que estos últimos pueden tener efectos negativos, como el bloqueo de dicho montón o una mayor fragmentación del índice.

La vista utiliza la importante vista del sistema sys.dm_db_index_physical_stats.

2. Creación de una tabla para almacenar los resultados de la desfragmentación del í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

Lo más importante de esta tabla es tener en cuenta la eliminación de datos (por ejemplo, datos que tienen más de 1 mes).

Los campos de la tabla serán comprensibles a partir del siguiente punto.

3. Crear un procedimiento almacenado para analizar y desfragmentar el índice seleccionado:

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. Crear una vista para ver las estadísticas de los resultados de la desfragmentación del í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

Esta vista se puede usar para notificar a los administradores diariamente sobre los resultados de la automatización de la desfragmentación del índice.

5. Crear una tarea en el Agente para ejecutar el procedimiento almacenado implementado

Aquí, necesitamos elegir el tiempo de una manera experimental. En mi caso, en algún lugar obtuve 5 minutos, en algún lugar, 1 hora.

Este algoritmo se puede expandir en varias bases de datos, pero en este caso, necesitamos un punto adicional 6:

Recopilación de todas las estadísticas de la automatización de desfragmentación de índices en un solo lugar para su posterior envío a los administradores.

Y ahora, me gustaría detenerme en las recomendaciones ya proporcionadas para el soporte de índices:

  1. La desfragmentación simultánea de todos los índices durante la carga mínima de la base de datos es inaceptable para los sistemas 24/7, ya que los índices se fragmentan constantemente y casi no hay tiempo en que la base de datos permanezca inactiva.
  2. Reorganización del índice de SQL Server:esta operación bloquea una tabla o partición (en el caso de un índice particionado), lo que no es bueno para los sistemas 24/7. Entonces, la reconstrucción del índice en el modo en tiempo real solo es compatible con la solución Enterprise y también puede provocar daños en los datos.

Este método no es óptimo, pero puede asegurar que los índices se desfragmenten correctamente (sin exceder el 30-40 % de la fragmentación) para su posterior uso por parte del optimizador para crear planes de ejecución.

Estaré agradecido por sus comentarios con pros y contras razonados de este enfoque, así como por las sugerencias alternativas probadas.

Referencias

  • Reorganizar y reconstruir índices
  • sys.dm_db_index_physical_stats

Herramienta útil:

dbForge Index Manager:práctico complemento de SSMS para analizar el estado de los índices SQL y solucionar problemas con la fragmentación de índices.