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:
- Crear una vista que muestre qué índices se han fragmentado y el porcentaje de índices fragmentados.
- Crear una tabla para almacenar los resultados de la desfragmentación del índice.
- Crear un procedimiento almacenado para analizar y desfragmentar el índice seleccionado.
- Crear una vista para ver las estadísticas de los resultados de la desfragmentación del índice.
- 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:
- 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.
- 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.