Förord
World Wide Web erbjuder en mängd information om SQL Server-indexdefragmentering eller SQL Server-indexombyggnad. De flesta av rekommendationerna hänvisar dock till databaser som har minsta laddningstid (oftast på natten).
Och hur är det med databaser som används för både datamodifiering och hämtning av information dygnet runt?
I den här artikeln kommer jag att tillhandahålla en mekanism för att automatisera SQL Server-indexdefragmentering implementerad i en databas som används i företaget jag arbetar för. Denna mekanism tillåter defragmentering av nödvändiga index på en regelbunden basis eftersom indexfragmentering sker konstant i 24/7-systemet. Ofta är detta inte tillräckligt för att utföra indexdefragmentering en gång om dagen.
Lösning
Låt oss först ta en titt på det allmänna tillvägagångssättet:
- Skapa en vy som visar vilka index som har fragmenterats och procentandelen av de fragmenterade indexen.
- Skapa en tabell för att lagra indexdefragmenteringsresultat.
- Skapa en lagrad procedur för att analysera och defragmentera det valda indexet.
- Skapa en vy för att visa statistik över indexdefragmenteringsresultaten.
- Skapa en uppgift i Agent för att köra den implementerade lagrade proceduren.
Och nu, låt oss ta en titt på implementeringen:
1. Skapa en vy som visar vilka index som har fragmenterats och procentandelen av de fragmenterade indexen:
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
Den här vyn visar endast index med fragmenteringsprocenten större än 30, d.v.s. index som kräver defragmentering. Den visar bara index som inte är högar, eftersom de senare kan leda till negativa effekter, som blockering av en sådan hög eller ytterligare indexfragmentering.
Vyn använder den viktiga systemvyn sys.dm_db_index_physical_stats.
2. Skapa en tabell för att lagra indexdefragmenteringsresultaten:
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
Det viktigaste med den här tabellen är att ha dataradering i åtanke (till exempel data som är äldre än 1 månad).
Tabellfält kommer att bli begripliga från nästa punkt.
3. Skapa en lagrad procedur för att analysera och defragmentera det valda indexet:
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. Skapa en vy för att visa statistiken för indexdefragmenteringsresultaten:
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
Den här vyn kan användas för att meddela administratörer dagligen om resultaten av automatiseringen av indexdefragmentering.
5. Skapa en uppgift i Agent för att köra den implementerade lagrade proceduren
Här måste vi välja tiden på ett experimentellt sätt. I mitt fall fick jag någonstans 5 minuter, någonstans – 1 timme.
Denna algoritm kan utökas på flera databaser, men i det här fallet behöver vi ytterligare en punkt 6:
Samlar all statistik för automatiseringen av indexdefragmentering på ett ställe för efterföljande sändning till administratörer.
Och nu skulle jag vilja uppehålla mig vid de redan tillhandahållna rekommendationerna för indexstöd:
- Samtidig defragmentering av alla index under den minimala databasbelastningen är oacceptabelt för 24/7-system, eftersom index ständigt fragmenteras och det nästan inte finns någon tid då databasen förblir inaktiv.
- SQL Server-indexomorganisering – denna operation blockerar en tabell eller partition (i fallet med ett partitionerat index), vilket inte är bra för 24/7-systemen. Sedan stöds återuppbyggnad av index i realtidsläge endast i Enterprise-lösningen och kan även leda till dataskada.
Den här metoden är inte optimal, men den kan framgångsrikt klara av att säkerställa att indexen defragmenteras ordentligt (inte överstiger 30-40 % av fragmenteringen) för deras efterföljande användning av optimeraren för att bygga utförandeplaner.
Jag kommer att vara tacksam för dina kommentarer med motiverade för- och nackdelar med detta tillvägagångssätt, såväl som för de testade alternativa förslagen.
Referenser
- Omorganisera och bygg om index
- sys.dm_db_index_physical_stats
Användbart verktyg:
dbForge Index Manager – praktiskt SSMS-tillägg för att analysera status för SQL-index och åtgärda problem med indexfragmentering.