Предговор
Световната мрежа предлага куп информация за дефрагментиране на индекс на SQL Server или възстановяване на индекс на SQL Server. Въпреки това, повечето от препоръките се отнасят до бази данни, които имат минимално време за зареждане (предимно през нощта).
А какво да кажем за бази данни, които се използват както за модификация на данни, така и за извличане на информация 24/7?
В тази статия ще предоставя механизъм за автоматизиране на дефрагментирането на индекс на SQL Server, внедрен в база данни, използвана във фирмата, в която работя. Този механизъм позволява редовно дефрагментиране на необходимите индекси, тъй като фрагментирането на индекса се извършва постоянно в 24/7 системата. Често това не е достатъчно за извършване на дефрагментиране на индекса веднъж на ден.
Решение
Първо, нека да разгледаме общия подход:
- Създаване на изглед, показващ кои индекси са били фрагментирани и процента на фрагментираните индекси.
- Създаване на таблица за съхраняване на резултатите от дефрагментирането на индекса.
- Създаване на съхранена процедура за анализиране и дефрагментиране на избрания индекс.
- Създаване на изглед за преглед на статистически данни на резултатите от дефрагментирането на индекса.
- Създаване на задача в агент за изпълнение на внедрената съхранена процедура.
И сега, нека да разгледаме реализацията:
1. Създаване на изглед, показващ кои индекси са били фрагментирани и процентът на фрагментираните индекси:
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
Този изглед показва само индекси с процент на фрагментация, по-голям от 30, т.е. индекси, които изискват дефрагментиране. Той показва само индекси, които не са купчини, тъй като последните могат да доведат до негативни ефекти, като блокиране на такава купчина или допълнително фрагментиране на индекса.
Изгледът използва важния системен изглед sys.dm_db_index_physical_stats.
2. Създаване на таблица за съхраняване на резултатите от дефрагментирането на индекса:
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
Най-важното нещо в тази таблица е да имате предвид изтриването на данни (например данни, които са по-стари от 1 месец).
Полетата на таблицата ще станат разбираеми от следващата точка.
3. Създаване на съхранена процедура за анализиране и дефрагментиране на избрания индекс:
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. Създаване на изглед за преглед на статистиката на резултатите от дефрагментирането на индекса:
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
Този изглед може да се използва за ежедневно известяване на администраторите за резултатите от автоматизацията на дефрагментирането на индекса.
5. Създаване на задача в Агента за изпълнение на внедрената съхранена процедура
Тук трябва да изберем времето по експериментален начин. В моя случай някъде имах 5 минути, някъде – 1 час.
Този алгоритъм може да бъде разширен в няколко бази данни, но в този случай се нуждаем от допълнителна точка 6:
Събиране на цялата статистика за автоматизацията на дефрагментирането на индекса на едно място за последващо изпращане до администраторите.
И сега бих искал да се спра на вече предоставените препоръки за поддръжка на индекси:
- Едновременното дефрагментиране на всички индекси по време на минимално натоварване на базата данни е неприемливо за денонощните системи, тъй като индексите са постоянно фрагментирани и почти няма време, когато базата данни остава неактивна.
- Реорганизация на индекса на SQL Server – тази операция блокира таблица или дял (в случай на разделен индекс), което не е добре за денонощните системи. След това възстановяването на индекса в режим в реално време се поддържа само в Enterprise решението и може също да доведе до увреждане на данните.
Този метод не е оптимален, но може успешно да се справи с гарантирането, че индексите са правилно дефрагментирани (не надвишаващи 30-40% от фрагментацията) за последващото им използване от оптимизатора за изграждане на планове за изпълнение.
Ще съм благодарен за вашите коментари с аргументирани плюсове и минуси на този подход, както и за изпробваните алтернативни предложения.
Препратки
- Реорганизирайте и изградете отново индекси
- sys.dm_db_index_physical_stats
Полезен инструмент:
dbForge Index Manager – удобна добавка за SSMS за анализиране на състоянието на SQL индексите и отстраняване на проблеми с фрагментацията на индекса.