sql >> Databasteknik >  >> RDS >> Sqlserver

Automatisera indexdefragmentering i MS SQL Server Database

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:

  1. Skapa en vy som visar vilka index som har fragmenterats och procentandelen av de fragmenterade indexen.
  2. Skapa en tabell för att lagra indexdefragmenteringsresultat.
  3. Skapa en lagrad procedur för att analysera och defragmentera det valda indexet.
  4. Skapa en vy för att visa statistik över indexdefragmenteringsresultaten.
  5. 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:

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


  1. Hur man skapar en användare i Oracle 11g och beviljar behörigheter

  2. MariaDB RTRIM() vs RTRIM_ORACLE():Vad är skillnaden?

  3. Hur kan jag logga och hitta de dyraste frågorna?

  4. Hur man tar bort en kolumn i SQL