Défragmentation conditionnelle des index

Même si cela n’est malheureusement pas suffisamment mis en place dans la pratique, il est rappelé qu’il est très important d’avoir des plans de maintenance des bases de données SQL Server, avec notamment une étape liées à la défragmentation des index.

Mais il ne faut pas trop en faire et faire chauffer le serveur pendant toutes les plages « hors production » pour simplement refaire des index qui n’ont presque pas évolué depuis la dernière fois.

Une bonne pratique consiste à s’appuyer sur le taux de fragmentation effectif des index afin de décider quelle type d’opération appliquer : rien du tout s’il n’est pas nécessaire d’intervenir, une petite réorganisation des données au sein des pages ou bien une reconstruction totale de l’index. Voici un script permettant de réaliser cette défragmentation conditionnelle.

Plusieurs solutions sont possibles pour s’occuper de toutes les bases de données ou bien se concentrer sur une seule base. J’ai fait ici le choix d’une procédure stockée qui peut être stockée dans un endroit centralisé (la base master par exemple) et qui est appelée une fois par base concernée, directement depuis la base.

use master
go
if not exists (select * from sys.procedures where object_id=OBJECT_ID('dbo.sp_IndexDefrag'))
	exec sp_executesql N'CREATE PROCEDURE dbo.sp_IndexDefrag AS RETURN;'
GO
ALTER PROCEDURE dbo.sp_IndexDefrag
	@Seuil_Reorganize tinyint = 10,
	@Seuil_Rebuild tinyint = 30
AS
BEGIN
	declare @Schema nvarchar(128),
			@Table nvarchar(128),
			@TableId int,
			@Index nvarchar(128),
			@IndexId int,
			@Partition int,
			@Frag float,
			@Online bit,
			@AvecPartitions bit

	declare @Requete nvarchar(max)

	declare @RebuildOnlineFeature bit,
			@RebuildTableKatmai bit,
			@RebuildOnlineDenali bit

	create table #Liste (ObjectId int,
						ObjectName nvarchar(128),
						SchemaName nvarchar(128),
						IndexId int,
						IndexName nvarchar(128),
						PartitionNumber int,
						Fragmentation float,
						OnlineRebuild bit,
						AvecPartitions bit DEFAULT 0)

	SET NOCOUNT ON

	-- Petite sécurité contre les valeurs d'entrée incorrectes
	if @Seuil_Reorganize>@Seuil_Rebuild
		SET @Seuil_Reorganize=@Seuil_Rebuild

	-- Détermination si le moteur est en édition Enterprise (seule cette édition supporte le rebuild Online)
	if SERVERPROPERTY('EngineEdition')=3 -- Enterprise, Evaluation ou Dev
		set @RebuildOnlineFeature=1
	else
		set @RebuildOnlineFeature=0

	-- Les heaps ne peuvent être reconstruits que depuis SQL Server 2008
	if convert(tinyint,left(convert(varchar(max),SERVERPROPERTY('ProductVersion')),patindex('%.%',convert(varchar(max),SERVERPROPERTY('ProductVersion')))-1))>=10
		set @RebuildTableKatmai=1
	else
		set @RebuildTableKatmai=0

	-- Les types de données BLOB (varchar(max), ...) ne supporte le REBUILD ONLINE qu'à partir de SQL Server 2012
	if convert(tinyint,left(convert(varchar(max),SERVERPROPERTY('ProductVersion')),patindex('%.%',convert(varchar(max),SERVERPROPERTY('ProductVersion')))-1))>=11
		set @RebuildOnlineDenali=1
	else
		set @RebuildOnlineDenali=0

	-- On récupère les taux de fragmentation des objets
	set @Requete = N'insert into #Liste (ObjectId, ObjectName, SchemaName, IndexId, IndexName, PartitionNumber, Fragmentation)
	select o.object_id,o.name,s.name,i.index_id,i.name,ps.partition_number,ps.avg_fragmentation_in_percent
		from sys.dm_db_index_physical_stats(DB_ID(),DEFAULT,DEFAULT,DEFAULT,Null) ps
			join sys.objects o on o.object_id=ps.object_id
								and o.type in (''U'', -- Table
												''V'') -- View
			join sys.schemas s on s.schema_id=o.schema_id
			join sys.indexes i on i.object_id=ps.object_id
								and i.index_id=ps.index_id
								and i.type<=2 -- Uniquement les heaps, les index clustered et non clustered
	WHERE ps.avg_fragmentation_in_percent>=' + convert(nvarchar,@Seuil_Reorganize) + N'
	--AND ps.page_count>8 -- Inutile de chercher à défragmenter si on ne dépasse pas un extend
	'
	exec sp_executesql @Requete

	-- Identification des index qui sont liés à des partitions (notamment parce que le Rebuid Online n'est pas possible)
	set @Requete = N'
	update l
	set AvecPartitions=1
	from #Liste l
	join (select i.object_id,i.index_id from sys.indexes i
		join sys.partition_schemes sch on sch.data_space_id=i.data_space_id
		join sys.partition_range_values rv on rv.function_id=sch.function_id
		group by i.object_id,i.index_id
		having count(*)>1) p on p.object_id=l.ObjectId
							and p.index_id=l.IndexId
	'
	exec sp_executesql @Requete
	-- On pour les reconstructions, on regarde si elles peuvent être faites en ligne
	update l
		set OnlineRebuild=@RebuildOnlineFeature
	FROM #Liste l
	where l.Fragmentation>=@Seuil_Rebuild

	if @RebuildOnlineFeature=1
	BEGIN
		set @Requete = N'
		update l
			set OnlineRebuild=0
		FROM #Liste l
			join sys.index_columns ic on ic.object_id=l.ObjectId
										and ic.index_id=l.IndexId
			join sys.columns c on c.object_id=ic.object_id
								and (c.column_id=ic.column_id or l.IndexId=1) -- L_index Clustered "contient" toutes les colonnes
								and (c.system_type_id in (34,35,99,241) -- Type image, text et ntext non supportés
									or (' + convert(varchar,@RebuildOnlineDenali) + N'=0 and c.max_length=-1) -- varchar(MAX), XML, ... avant SQL Server 2012
									or c.is_filestream=1) -- Filestream'
		exec sp_executesql @Requete
 	END

	-- Traitement des heaps (tables sans index clustered)
	-- On ne traite que les reconstructions
	if @RebuildTableKatmai=1
	BEGIN
		DECLARE Curseur_Tables INSENSITIVE CURSOR FOR
		select l.SchemaName,l.ObjectName,l.PartitionNumber,l.OnlineRebuild,l.AvecPartitions
		from #Liste l
		WHERE l.Fragmentation>=@Seuil_Rebuild
			and l.IndexId=0

		OPEN Curseur_Tables
		FETCH NEXT FROM Curseur_Tables INTO @Schema,@Table,@Partition,@Online,@AvecPartitions

		WHILE @@FETCH_STATUS = 0
		BEGIN
			set @Requete=N'ALTER TABLE ' + QUOTENAME(@Schema) + N'.' + QUOTENAME(@Table) + N' REBUILD '
						+ case when @AvecPartitions=1
								then N'PARTITION = ' + convert(nvarchar(max),@Partition)
								else N''
								END
			exec sp_executesql @Requete

			FETCH NEXT FROM Curseur_Tables INTO @Schema,@Table,@Partition,@Online,@AvecPartitions
		END

		CLOSE Curseur_Tables
		DEALLOCATE Curseur_Tables
	END

	-- Traitement des index : d'abord les clustered puis les non clustered
	DECLARE Curseur_Index INSENSITIVE CURSOR FOR
	select l.SchemaName,l.ObjectName,l.IndexName,l.partitionnumber,l.Fragmentation,l.OnlineRebuild,l.AvecPartitions
	from #Liste l
	where IndexId>=1
	order by l.IndexId -- Pour traiter d'abord les index clustered

 	OPEN Curseur_Index
	FETCH NEXT FROM Curseur_Index INTO @Schema,@Table,@Index,@Partition,@Frag,@Online,@AvecPartitions
	WHILE @@FETCH_STATUS = 0
	BEGIN
		set @Requete=N'ALTER INDEX ' + QUOTENAME(@Index) 
					+ N' ON ' + QUOTENAME(@Schema) + N'.' + QUOTENAME(@Table) 
					+ CASE WHEN @Frag>=@Seuil_Rebuild THEN N' REBUILD ' ELSE N' REORGANIZE ' END
					+ case when @AvecPartitions=1
							then N'PARTITION = ' + convert(nvarchar(max),@Partition)
							else case when @Frag>=@Seuil_Rebuild
									then N' WITH (ONLINE=' + case when @Online=1 then N'ON' else N'OFF' end + N')'
									else N''
									end
							end		exec sp_executesql @Requete

		FETCH NEXT FROM Curseur_Index INTO @Schema,@Table,@Index,@Partition,@Frag,@Online,@AvecPartitions
	END

	CLOSE Curseur_Index
	DEALLOCATE Curseur_Index

END
GO

Après, il n’y a plus qu’à vous positionner sur la base à traiter, et à lancer la procédure stockée, éventuellement avec les paramètres adéquats si vous souhaitez d’autres valeurs de seuils que celles par défaut.

Une réflexion au sujet de « Défragmentation conditionnelle des index »

  1. Ping : SQLServer.fr » Reconstruction de tous les index d’une base

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Contrôle de sécurité * Délai de réponse expiré. Merci de compléter à nouveau le code de contrôle.