Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Automatyzacja defragmentacji indeksów w bazie danych MS SQL Server

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:

  1. Tworzenie widoku pokazującego, które indeksy zostały pofragmentowane i procent pofragmentowanych indeksów.
  2. Tworzenie tabeli do przechowywania wyników defragmentacji indeksu.
  3. Tworzenie procedury składowanej do analizy i defragmentacji wybranego indeksu.
  4. Tworzenie widoku do przeglądania statystyk wyników defragmentacji indeksu.
  5. 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:

  1. 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.
  2. 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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak stworzyć linked server dla SQL Server 2008 gdzie mamy bazę danych z 2000 i 2005

  2. Jak przekazać parametry z wartościami przechowywanymi w tabeli z java do procedury składowanej serwera sql?

  3. SQL Server zużywa dużo procesora podczas wyszukiwania wewnątrz ciągów nvarchar

  4. SQL Server:Jak używać UNION z dwoma zapytaniami, które OBA mają klauzulę WHERE?

  5. Test wydajności MS SQL Server w systemie Linux i Windows w celu wykrycia różnicy