Vorwort
Das World Wide Web bietet eine Reihe von Informationen zur Defragmentierung von SQL Server-Indizes oder zur Neuerstellung von SQL Server-Indizes. Die meisten Empfehlungen beziehen sich jedoch auf Datenbanken mit minimaler Ladezeit (meistens nachts).
Und was ist mit Datenbanken, die sowohl zur Datenänderung als auch zum Abrufen von Informationen rund um die Uhr verwendet werden?
In diesem Artikel stelle ich einen Mechanismus zum Automatisieren der SQL Server-Indexdefragmentierung bereit, der in einer Datenbank implementiert ist, die in dem Unternehmen verwendet wird, für das ich arbeite. Dieser Mechanismus ermöglicht es, benötigte Indizes regelmäßig zu defragmentieren, da die Indexfragmentierung im 24/7-System ständig stattfindet. Oft reicht dies nicht aus, um einmal täglich eine Index-Defragmentierung durchzuführen.
Lösung
Schauen wir uns zunächst den allgemeinen Ansatz an:
- Erstellen einer Ansicht, die anzeigt, welche Indizes fragmentiert wurden und den Prozentsatz der fragmentierten Indizes.
- Erstellen einer Tabelle zum Speichern der Ergebnisse der Indexdefragmentierung.
- Erstellen einer gespeicherten Prozedur zum Analysieren und Defragmentieren des ausgewählten Index.
- Erstellen einer Ansicht zum Anzeigen von Statistiken der Ergebnisse der Indexdefragmentierung.
- Erstellen einer Aufgabe im Agenten zum Ausführen der implementierten gespeicherten Prozedur.
Schauen wir uns nun die Implementierung an:
1. Erstellen einer Ansicht, die anzeigt, welche Indizes fragmentiert wurden, und den Prozentsatz der fragmentierten Indizes:
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
Diese Ansicht zeigt nur Indizes mit einem Fragmentierungsprozentsatz von mehr als 30, d. h. Indizes, die defragmentiert werden müssen. Es werden nur Indizes angezeigt, die keine Heaps sind, da letztere zu negativen Effekten führen können, wie z. B. das Blockieren eines solchen Heaps oder eine weitere Indexfragmentierung.
Die Ansicht verwendet die wichtige Systemansicht sys.dm_db_index_physical_stats.
2. Erstellen einer Tabelle zum Speichern der Ergebnisse der Index-Defragmentierung:
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
Das Wichtigste an dieser Tabelle ist die Datenlöschung (z. B. Daten, die älter als 1 Monat sind).
Tabellenfelder werden ab dem nächsten Punkt verständlich.
3. Erstellen einer gespeicherten Prozedur zum Analysieren und Defragmentieren des ausgewählten Indexes:
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. Erstellen einer Ansicht zum Anzeigen der Statistiken der Ergebnisse der Indexdefragmentierung:
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
Diese Ansicht kann verwendet werden, um Administratoren täglich über die Ergebnisse der Automatisierung der Indexdefragmentierung zu informieren.
5. Erstellen einer Aufgabe im Agenten zum Ausführen der implementierten gespeicherten Prozedur
Hier müssen wir die Zeit auf experimentelle Weise auswählen. In meinem Fall habe ich irgendwo 5 Minuten, irgendwo – 1 Stunde.
Dieser Algorithmus kann auf mehrere Datenbanken erweitert werden, aber in diesem Fall benötigen wir einen zusätzlichen Punkt 6:
Sammeln aller Statistiken der Automatisierung der Index-Defragmentierung an einem Ort, um sie später an Administratoren zu senden.
Und nun möchte ich auf die bereits gegebenen Empfehlungen zur Indexunterstützung eingehen:
- Die gleichzeitige Defragmentierung aller Indizes während der minimalen Datenbanklast ist für die 24/7-Systeme nicht akzeptabel, da die Indizes ständig fragmentiert werden und es fast keine Zeit gibt, in der die Datenbank im Leerlauf bleibt.
- SQL Server-Indexreorganisation – dieser Vorgang blockiert eine Tabelle oder Partition (im Fall eines partitionierten Index), was für die 24/7-Systeme nicht gut ist. Dann wird der Indexneuaufbau im Echtzeitmodus nur in der Enterprise-Lösung unterstützt und kann auch zu Datenschäden führen.
Diese Methode ist nicht optimal, aber sie kann erfolgreich sicherstellen, dass Indizes ordnungsgemäß defragmentiert werden (nicht mehr als 30-40 % der Fragmentierung) für ihre nachfolgende Verwendung durch den Optimierer zum Erstellen von Ausführungsplänen.
Ich bin dankbar für Ihre Kommentare mit begründeten Vor- und Nachteilen dieses Ansatzes sowie für die getesteten alternativen Vorschläge.
Referenzen
- Indizes neu organisieren und neu erstellen
- sys.dm_db_index_physical_stats
Nützliches Tool:
dbForge Index Manager – praktisches SSMS-Add-in zum Analysieren des Status von SQL-Indizes und Beheben von Problemen mit der Indexfragmentierung.