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.
Ping : SQLServer.fr » Reconstruction de tous les index d’une base