Przedmowa
Sieć World Wide Web oferuje mnóstwo informacji na temat defragmentacji indeksu SQL Server lub odbudowy indeksu SQL Server. Jednak większość zaleceń odnosi się do baz danych, które mają minimalny czas ładowania (głównie w nocy).
A co z bazami danych, które są używane zarówno do modyfikacji danych, jak i pobierania informacji przez całą dobę?
W tym artykule przedstawię mechanizm automatyzacji defragmentacji indeksów SQL Server zaimplementowany w bazie danych używanej w firmie, w której pracuję. Mechanizm ten umożliwia defragmentację wymaganych indeksów na bieżąco, ponieważ fragmentacja indeksów odbywa się stale w systemie 24/7. Często to nie wystarcza do wykonania defragmentacji indeksu raz dziennie.
Rozwiązanie
Najpierw przyjrzyjmy się ogólnemu podejściu:
- Tworzenie widoku pokazującego, które indeksy zostały pofragmentowane i procent pofragmentowanych indeksów.
- Tworzenie tabeli do przechowywania wyników defragmentacji indeksu.
- Tworzenie procedury składowanej do analizy i defragmentacji wybranego indeksu.
- Tworzenie widoku do przeglądania statystyk wyników defragmentacji indeksu.
- Tworzenie w agencie zadania do uruchomienia zaimplementowanej procedury składowanej.
A teraz spójrzmy na implementację:
1. Tworzenie widoku pokazującego, które indeksy zostały pofragmentowane i procent pofragmentowanych indeksów:
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
Ten widok pokazuje tylko indeksy z procentem fragmentacji większym niż 30, czyli indeksy wymagające defragmentacji. Pokazuje tylko indeksy, które nie są stertami, ponieważ te ostatnie mogą prowadzić do negatywnych skutków, takich jak zablokowanie takiej sterty lub dalsza fragmentacja indeksu.
Widok używa ważnego widoku systemowego sys.dm_db_index_physical_stats.
2. Tworzenie tabeli do przechowywania wyników defragmentacji indeksu:
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
Najważniejszą rzeczą w tej tabeli jest pamiętanie o usuwaniu danych (na przykład danych starszych niż 1 miesiąc).
Pola tabeli staną się zrozumiałe od następnego punktu.
3. Tworzenie procedury składowanej do analizy i defragmentacji wybranego indeksu:
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. Tworzenie widoku do przeglądania statystyk wyników defragmentacji indeksu:
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
Ten widok może być używany do codziennego powiadamiania administratorów o wynikach automatyzacji defragmentacji indeksu.
5. Tworzenie zadania w agencie do uruchomienia zaimplementowanej procedury składowanej
Tutaj musimy wybrać czas w sposób eksperymentalny. W moim przypadku gdzieś mam 5 minut, gdzieś – 1 godzinę.
Algorytm ten można rozszerzyć na kilka baz danych, ale w tym przypadku potrzebujemy dodatkowego punktu 6:
Zbieranie wszystkich statystyk automatyzacji defragmentacji indeksów w jednym miejscu w celu późniejszego wysłania do administratorów.
A teraz chciałbym zatrzymać się na już dostarczonych zaleceniach dotyczących wsparcia indeksów:
- Jednoczesna defragmentacja wszystkich indeksów przy minimalnym obciążeniu bazy danych jest nie do przyjęcia dla systemów 24/7, ponieważ indeksy są stale fragmentowane i prawie nie ma czasu, kiedy baza danych pozostaje bezczynna.
- Reorganizacja indeksu SQL Server – ta operacja blokuje tabelę lub partycję (w przypadku indeksu partycjonowanego), co nie jest dobre dla systemów 24/7. Wówczas odbudowa indeksu w trybie czasu rzeczywistego jest obsługiwana tylko w rozwiązaniu Enterprise i może również prowadzić do uszkodzenia danych.
Ta metoda nie jest optymalna, ale z powodzeniem radzi sobie z zapewnieniem odpowiedniej defragmentacji indeksów (nie przekraczającej 30-40% fragmentacji) w celu ich późniejszego wykorzystania przez optymalizator do budowania planów wykonania.
Będę wdzięczny za Wasze komentarze z uzasadnionymi zaletami i wadami tego podejścia, a także za przetestowane alternatywne sugestie.
Referencje
- Reorganizuj i odbuduj indeksy
- sys.dm_db_index_physical_stats
Przydatne narzędzie:
dbForge Index Manager – poręczny dodatek SSMS do analizy stanu indeksów SQL i rozwiązywania problemów z fragmentacją indeksów.