Автоматизация дефрагментации индексов в базе данных MS SQL Server

от автора

Предисловие:

В интернете можно найти массу информации о дефрагментации или перестроению индексов. Однако, большинство рекомендаций направлены на базы данных, у которых есть время минимальной нагрузки (в основном ночью).

А как быть с базами данных, которые постоянно используются как для изменения данных, так и для получения информации 24 часа в сутки, 7 дней в неделю?

В данной статье приведу реализованный механизм автоматизации дефрагментации индексов в базе данных для поддержки базы данных у нас на предприятии. Этот механизм позволяет все время дефрагментировать нужные индексы, т к в системе 24×7 фрагментация индексов происходит постоянно. И часто дефрагментация даже 1 раз в день для индексов недостаточна.

Решение:

Сначала общий подход:
1) создать для нужной базы данных представление, с помощью которого можно получить какие индексы и на сколько в процентах дефрагментированы
2) создать таблицу для сохранения результатов дефрагментации индексов
3) создать хранимую процедуру, которая и будет анализировать и дефрагментировать выбранный индекс
4) создать представление для просмотра статистики по результатам дефрагментации индексов
5) создать задание в Агенте, которое будет запускать реализованную хранимую процедуру в п.3.

А теперь реализация:
1) создать для нужной базы данных представление, с помощью которого можно получить какие индексы и на сколько в процентах дефрагментированы:

USE [Название_Базы_Данных] 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'Название_Базы_Данных') 	, 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 [Название_Базы_Данных] 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 

Главное-в этой таблице не забывать удалять данные (например, которым больше месяца или еще чаще).
Поля в таблице будут понятны по следующему пункту.

3) создать хранимую процедуру, которая и будет анализировать и дефрагментировать выбранный индекс:

USE [Название_Базы_Данных] GO  SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO  CREATE PROCEDURE [srv].[AutoDefragIndex] AS BEGIN 	SET NOCOUNT ON;  	--объявляем необходимые переменные 	declare @IndexName nvarchar(100) --название индекса 	,@db nvarchar(100)			 --название базы данных 	,@Shema nvarchar(100)			 --название схемы 	,@Table nvarchar(100)			 --название таблицы 	,@SQL_Str nvarchar (2000)		 --строка для формирования команды 	,@frag decimal(6,2)				 --% фрагментации до процесса дефрагментации 	,@frag_after decimal(6,2)		 --% фрагментации после процесса дефрагментации         --Количество фрагментов на конечном уровне единицы распределения IN_ROW_DATA	         ,@frag_num int				  	,@func int					 --round(i.avg_fragmentation_in_percent*i.page_count,0) 	,@page int					 --кол-во страниц индекса		 	,@rec int						 --общее кол-во записей 	,@ts datetime					 --дата и время начала дефрагментации 	,@tf datetime					 --дата и время окончания дефрагментации 	--идентификатор объекта таблицы или представления, для которых создан индекс         ,@object_id int					  	,@idx int;						 --ID индекса  	--получаем текущую дату и время 	set @ts = getdate(); 	 	--получаем очередной индекс для дефрагментации 	--здесь именно важный индекс выбирается. При этом никогда не случиться, что один индекс будет         --постоянно дефрагментироваться, а все остальные не будут выбраны для дефрагментации 	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(@db is not null) 	begin 	   --непосредственно реорганизация индекса 	   set @SQL_Str = 'alter index ['+@IndexName+'] on ['+@Shema+'].['+@Table+'] Reorganize';  		execute sp_executesql  @SQL_Str;  		--получаем текущую дату и время 		set @tf = getdate()  		--получаем процент дефрагментации после дефрагментации 		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;  		--записываем результат работы 		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; 		 		--обновляем статистику для индекса 		set @SQL_Str = 'UPDATE STATISTICS ['+@Shema+'].['+@Table+'] ['+@IndexName+']';  		execute sp_executesql  @SQL_Str; 	end END 

4) создать представление для просмотра статистики по результатам дефрагментации индексов:

USE [Название_Базы_Данных] 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) создать задание в Агенте, которое будет запускать реализованную хранимую процедуру в п.3:
Здесь нужно подбирать время экспериментальным путем. У меня получилось где-то 5 минут, а где-то и час.

Данный алгоритм можно расширить на несколько баз данных, но тогда нужно ввести еще и п.6:
Собрать всю статистику по автоматизации дефрагментации индексов в базах данных в одном месте для последующей отправки администраторам.

А теперь хотелось бы остановиться поподробнее о уже выложенных рекомендациях по поддержке индексов:
1) дефрагментация сразу всех индексов во время минимальной нагрузки базы данных — для систем 24×7 это недопустимо, т к индексы фрагментируются постоянно и времени простоя базы данных практически не бывает
2) перестроение индекса — данная операция блокирует таблицу или секцию (в случае секционированного индекса), что для систем 24×7 не есть хорошо. Далее, перестроение индекса в режиме реального времени поддерживается только в Enterprise-решении, а также может привести к повреждению данных.

Данный метод не является оптимальным, но успешно справляется с тем, чтобы индексы были достаточно дефрагментированы (не выше 30-40% фрагментации) для использования их оптимизатором для построения планов выполнения.

Буду очень признателен, если в комментариях появятся аргументированные плюсы и минусы данного подхода, а также проверенные альтернативные предложения.

Источники:
Реорганизация и перестроение индексов
sys.dm_db_index_physical_stats
ссылка на оригинал статьи https://habrahabr.ru/post/314454/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *