Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

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

Предговор

Световната мрежа предлага куп информация за дефрагментиране на индекс на SQL Server или възстановяване на индекс на SQL Server. Въпреки това, повечето от препоръките се отнасят до бази данни, които имат минимално време за зареждане (предимно през нощта).

А какво да кажем за бази данни, които се използват както за модификация на данни, така и за извличане на информация 24/7?

В тази статия ще предоставя механизъм за автоматизиране на дефрагментирането на индекс на SQL Server, внедрен в база данни, използвана във фирмата, в която работя. Този механизъм позволява редовно дефрагментиране на необходимите индекси, тъй като фрагментирането на индекса се извършва постоянно в 24/7 системата. Често това не е достатъчно за извършване на дефрагментиране на индекса веднъж на ден.

Решение

Първо, нека да разгледаме общия подход:

  1. Създаване на изглед, показващ кои индекси са били фрагментирани и процента на фрагментираните индекси.
  2. Създаване на таблица за съхраняване на резултатите от дефрагментирането на индекса.
  3. Създаване на съхранена процедура за анализиране и дефрагментиране на избрания индекс.
  4. Създаване на изглед за преглед на статистически данни на резултатите от дефрагментирането на индекса.
  5. Създаване на задача в агент за изпълнение на внедрената съхранена процедура.

И сега, нека да разгледаме реализацията:

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:

Събиране на цялата статистика за автоматизацията на дефрагментирането на индекса на едно място за последващо изпращане до администраторите.

И сега бих искал да се спра на вече предоставените препоръки за поддръжка на индекси:

  1. Едновременното дефрагментиране на всички индекси по време на минимално натоварване на базата данни е неприемливо за денонощните системи, тъй като индексите са постоянно фрагментирани и почти няма време, когато базата данни остава неактивна.
  2. Реорганизация на индекса на SQL Server – тази операция блокира таблица или дял (в случай на разделен индекс), което не е добре за денонощните системи. След това възстановяването на индекса в режим в реално време се поддържа само в Enterprise решението и може също да доведе до увреждане на данните.

Този метод не е оптимален, но може успешно да се справи с гарантирането, че индексите са правилно дефрагментирани (не надвишаващи 30-40% от фрагментацията) за последващото им използване от оптимизатора за изграждане на планове за изпълнение.

Ще съм благодарен за вашите коментари с аргументирани плюсове и минуси на този подход, както и за изпробваните алтернативни предложения.

Препратки

  • Реорганизирайте и изградете отново индекси
  • sys.dm_db_index_physical_stats

Полезен инструмент:

dbForge Index Manager – удобна добавка за SSMS за анализиране на състоянието на SQL индексите и отстраняване на проблеми с фрагментацията на индекса.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Филтрирайте по изходна клауза sql

  2. Възможно ли е да се използва пълнотекстово търсене (FTS) с LINQ?

  3. Как да съхранявате пароли в базата данни сигурно?

  4. Вмъкване на няколко реда БЕЗ повтаряне на частта INSERT INTO ... от израза?

  5. EXEC sp_executesql с множество параметри