Préface
Le World Wide Web offre un tas d'informations sur la défragmentation d'index SQL Server ou la reconstruction d'index SQL Server. Cependant, la plupart des recommandations font référence à des bases de données qui ont un temps de chargement minimum (principalement la nuit).
Et qu'en est-il des bases de données utilisées à la fois pour la modification des données et la récupération d'informations 24h/24 et 7j/7 ?
Dans cet article, je fournirai un mécanisme d'automatisation de la défragmentation d'index SQL Server implémenté dans une base de données utilisée dans l'entreprise pour laquelle je travaille. Ce mécanisme permet de défragmenter régulièrement les index requis puisque la fragmentation des index a lieu en permanence dans le système 24h/24 et 7j/7. Souvent, cela ne suffit pas pour effectuer une défragmentation d'index une fois par jour.
Solution
Tout d'abord, regardons l'approche générale :
- Création d'une vue indiquant les index qui ont été fragmentés et le pourcentage d'index fragmentés.
- Créer une table pour stocker les résultats de la défragmentation d'index.
- Création d'une procédure stockée pour analyser et défragmenter l'index sélectionné.
- Création d'une vue pour afficher les statistiques des résultats de la défragmentation de l'index.
- Création d'une tâche dans l'Agent pour exécuter la procédure stockée mise en œuvre.
Et maintenant, regardons la mise en œuvre :
1. Création d'une vue montrant quels index ont été fragmentés et le pourcentage des index fragmentés :
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
Cette vue affiche uniquement les index dont le pourcentage de fragmentation est supérieur à 30, c'est-à-dire les index qui nécessitent une défragmentation. Il affiche uniquement les index qui ne sont pas des tas, car ces derniers peuvent entraîner des effets négatifs, comme le blocage d'un tel tas ou une fragmentation supplémentaire de l'index.
La vue utilise la vue système importante sys.dm_db_index_physical_stats.
2. Création d'une table pour stocker les résultats de la défragmentation de l'index :
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
La chose la plus importante à propos de ce tableau est de garder à l'esprit la suppression des données (par exemple, les données datant de plus d'un mois).
Les champs du tableau deviendront compréhensibles à partir du point suivant.
3. Création d'une procédure stockée pour analyser et défragmenter l'index sélectionné :
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. Création d'une vue pour afficher les statistiques des résultats de la défragmentation de l'index :
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
Cette vue peut être utilisée pour informer quotidiennement les administrateurs des résultats de l'automatisation de la défragmentation de l'index.
5. Création d'une tâche dans l'Agent pour exécuter la procédure stockée implémentée
Ici, nous devons choisir le temps de manière expérimentale. Dans mon cas, quelque part j'ai eu 5 minutes, quelque part – 1 heure.
Cet algorithme peut être étendu sur plusieurs bases de données, mais dans ce cas, nous avons besoin d'un point supplémentaire 6 :
Rassembler toutes les statistiques de l'automatisation de la défragmentation de l'index en un seul endroit pour les envoyer ultérieurement aux administrateurs.
Et maintenant, je voudrais m'attarder sur les recommandations déjà fournies pour la prise en charge de l'index :
- La défragmentation simultanée de tous les index pendant la charge minimale de la base de données est inacceptable pour les systèmes fonctionnant 24h/24 et 7j/7, car les index sont constamment fragmentés et il n'y a presque pas de temps pendant lequel la base de données reste inactive.
- Réorganisation d'index SQL Server :cette opération bloque une table ou une partition (dans le cas d'un index partitionné), ce qui n'est pas bon pour les systèmes fonctionnant 24h/24 et 7j/7. Ensuite, la reconstruction d'index en mode temps réel n'est prise en charge que dans la solution Enterprise et peut également endommager les données.
Cette méthode n'est pas optimale, mais elle permet de s'assurer que les index sont correctement défragmentés (ne dépassant pas 30 à 40 % de fragmentation) pour leur utilisation ultérieure par l'optimiseur pour la construction de plans d'exécution.
Je serai reconnaissant pour vos commentaires avec les avantages et les inconvénients raisonnés de cette approche, ainsi que pour les suggestions alternatives testées.
Références
- Réorganiser et reconstruire les index
- sys.dm_db_index_physical_stats
Outil utile :
dbForge Index Manager - complément SSMS pratique pour analyser l'état des index SQL et résoudre les problèmes de fragmentation d'index.