Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Automatizzazione della deframmentazione degli indici nel database di MS SQL Server

Prefazione

Il World Wide Web offre una serie di informazioni sulla deframmentazione dell'indice di SQL Server o sulla ricostruzione dell'indice di SQL Server. Tuttavia, la maggior parte dei consigli si riferisce a database che hanno un tempo di caricamento minimo (per lo più di notte).

E che dire dei database utilizzati sia per la modifica dei dati che per il recupero di informazioni 24 ore su 24, 7 giorni su 7?

In questo articolo, fornirò un meccanismo per automatizzare la deframmentazione dell'indice di SQL Server implementato in un database utilizzato nell'azienda per cui lavoro. Questo meccanismo consente di deframmentare gli indici richiesti su base regolare poiché la frammentazione degli indici avviene costantemente nel sistema 24 ore su 24, 7 giorni su 7. Spesso, questo non è sufficiente per eseguire la deframmentazione dell'indice una volta al giorno.

Soluzione

Innanzitutto, diamo un'occhiata all'approccio generale:

  1. Creazione di una vista che mostra quali indici sono stati frammentati e la percentuale degli indici frammentati.
  2. Creazione di una tabella per la memorizzazione dei risultati della deframmentazione dell'indice.
  3. Creazione di una procedura memorizzata per l'analisi e la deframmentazione dell'indice selezionato.
  4. Creazione di una vista per la visualizzazione delle statistiche dei risultati della deframmentazione dell'indice.
  5. Creazione di un'attività in Agent per l'esecuzione della procedura memorizzata implementata.

E ora, diamo un'occhiata all'implementazione:

1. Creazione di una vista che mostra quali indici sono stati frammentati e la percentuale degli indici frammentati:

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

Questa visualizzazione mostra solo gli indici con la percentuale di frammentazione maggiore di 30, ovvero gli indici che richiedono la deframmentazione. Mostra solo gli indici che non sono heap, poiché questi ultimi possono portare a effetti negativi, come il blocco di tale heap o un'ulteriore frammentazione dell'indice.

La vista utilizza l'importante vista di sistema sys.dm_db_index_physical_stats.

2. Creazione di una tabella per la memorizzazione dei risultati della deframmentazione dell'indice:

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

La cosa più importante di questa tabella è tenere a mente l'eliminazione dei dati (ad esempio, i dati che risalgono a più di 1 mese).

I campi della tabella diventeranno comprensibili dal punto successivo.

3. Creazione di una procedura memorizzata per l'analisi e la deframmentazione dell'indice selezionato:

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. Creazione di una vista per la visualizzazione delle statistiche dei risultati della deframmentazione dell'indice:

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

Questa visualizzazione può essere utilizzata per notificare quotidianamente agli amministratori i risultati dell'automazione della deframmentazione dell'indice.

5. Creazione di un'attività in Agent per l'esecuzione della procedura memorizzata implementata

Qui, dobbiamo scegliere il tempo in modo sperimentale. Nel mio caso, da qualche parte ho 5 minuti, da qualche parte – 1 ora.

Questo algoritmo può essere espanso su più database, ma in questo caso abbiamo bisogno di un ulteriore punto 6:

Raccolta di tutte le statistiche dell'automazione della deframmentazione dell'indice in un unico posto per il successivo invio agli amministratori.

E ora, vorrei soffermarmi sui consigli già forniti per il supporto dell'indice:

  1. La deframmentazione simultanea di tutti gli indici durante il carico minimo del database è inaccettabile per i sistemi 24 ore su 24, 7 giorni su 7, poiché gli indici sono costantemente frammentati e non c'è quasi tempo in cui il database rimane inattivo.
  2. Riorganizzazione dell'indice di SQL Server:questa operazione blocca una tabella o una partizione (nel caso di un indice partizionato), che non va bene per i sistemi 24/7. Quindi, la ricostruzione dell'indice in modalità in tempo reale è supportata solo nella soluzione Enterprise e può anche causare danni ai dati.

Questo metodo non è ottimale, ma può far fronte con successo a garantire che gli indici siano adeguatamente deframmentati (non superiore al 30-40% della frammentazione) per il loro successivo utilizzo da parte dell'ottimizzatore per la creazione di piani di esecuzione.

Ti sarò grato per i tuoi commenti con pro e contro motivati ​​di questo approccio, nonché per i suggerimenti alternativi testati.

Riferimenti

  • Riorganizza e ricostruisci indici
  • sys.dm_db_index_physical_stats

Strumento utile:

dbForge Index Manager – pratico componente aggiuntivo SSMS per analizzare lo stato degli indici SQL e risolvere i problemi con la frammentazione degli indici.