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:
- Creazione di una vista che mostra quali indici sono stati frammentati e la percentuale degli indici frammentati.
- Creazione di una tabella per la memorizzazione dei risultati della deframmentazione dell'indice.
- Creazione di una procedura memorizzata per l'analisi e la deframmentazione dell'indice selezionato.
- Creazione di una vista per la visualizzazione delle statistiche dei risultati della deframmentazione dell'indice.
- 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:
- 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.
- 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.