sql >> Database >  >> RDS >> Sqlserver

Indexdefragmentatie automatiseren in MS SQL Server-database

Voorwoord

Het World Wide Web biedt een heleboel informatie over het defragmenteren van SQL Server-indexen of het opnieuw opbouwen van de SQL Server-index. De meeste aanbevelingen verwijzen echter naar databases met een minimale laadtijd (meestal 's nachts).

En hoe zit het met databases die voor beide worden gebruikt, voor het aanpassen van gegevens en het 24/7 opvragen van informatie?

In dit artikel zal ik een mechanisme bieden voor het automatiseren van de defragmentatie van de SQL Server-index, geïmplementeerd in een database die wordt gebruikt in het bedrijf waarvoor ik werk. Met dit mechanisme kunnen de vereiste indexen regelmatig worden gedefragmenteerd, aangezien indexfragmentatie constant plaatsvindt in het 24/7-systeem. Vaak is dit niet voldoende om de index eenmaal per dag te defragmenteren.

Oplossing

Laten we eerst eens kijken naar de algemene benadering:

  1. Een weergave maken die laat zien welke indexen zijn gefragmenteerd en het percentage van de gefragmenteerde indexen.
  2. Een tabel maken voor het opslaan van indexdefragmentatieresultaten.
  3. Een opgeslagen procedure maken voor het analyseren en defragmenteren van de geselecteerde index.
  4. Een weergave maken voor het bekijken van statistieken van de resultaten van de indexdefragmentatie.
  5. Een taak maken in Agent voor het uitvoeren van de geïmplementeerde opgeslagen procedure.

En laten we nu eens kijken naar de implementatie:

1. Een weergave maken die laat zien welke indexen zijn gefragmenteerd en het percentage van de gefragmenteerde 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

Deze weergave toont alleen indexen met een fragmentatiepercentage groter dan 30, d.w.z. indexen die defragmentatie vereisen. Het toont alleen indexen die geen heaps zijn, aangezien de laatste kunnen leiden tot negatieve effecten, zoals blokkering van zo'n heap of verdere fragmentatie van de index.

De weergave gebruikt de belangrijke systeemweergave sys.dm_db_index_physical_stats.

2. Een tabel maken om de resultaten van de indexdefragmentatie op te slaan:

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

Het belangrijkste van deze tabel is dat u rekening houdt met het verwijderen van gegevens (bijvoorbeeld gegevens die ouder zijn dan 1 maand).

Tabelvelden worden vanaf het volgende punt begrijpelijk.

3. Een opgeslagen procedure maken voor het analyseren en defragmenteren van de geselecteerde index:

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. Een weergave maken voor het bekijken van de statistieken van de resultaten van de indexdefragmentatie:

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

Deze weergave kan worden gebruikt om beheerders dagelijks op de hoogte te stellen van de resultaten van de automatisering van indexdefragmentatie.

5. Een taak maken in Agent voor het uitvoeren van de geïmplementeerde opgeslagen procedure

Hier moeten we de tijd op een experimentele manier kiezen. In mijn geval heb ik ergens 5 minuten, ergens - 1 uur.

Dit algoritme kan worden uitgebreid naar verschillende databases, maar in dit geval hebben we een extra punt 6 nodig:

Alle statistieken van de automatisering van indexdefragmentatie op één plek verzamelen om ze vervolgens naar beheerders te verzenden.

En nu wil ik even stilstaan ​​bij de reeds gegeven aanbevelingen voor indexondersteuning:

  1. Gelijktijdige defragmentatie van alle indexen tijdens de minimale databasebelasting is onaanvaardbaar voor de 24/7 systemen, aangezien indexen constant gefragmenteerd zijn en er bijna geen tijd is dat de database inactief blijft.
  2. SQL Server index reorganisatie – deze operatie blokkeert een tabel of partitie (in het geval van een gepartitioneerde index), wat niet goed is voor de 24/7 systemen. Vervolgens wordt het opnieuw opbouwen van de index in de realtime modus alleen ondersteund in de Enterprise-oplossing en kan dit ook leiden tot gegevensbeschadiging.

Deze methode is niet optimaal, maar kan er met succes voor zorgen dat indexen correct worden gedefragmenteerd (niet meer dan 30-40% fragmentatie) voor later gebruik door de optimizer voor het bouwen van uitvoeringsplannen.

Ik zal dankbaar zijn voor uw opmerkingen met gemotiveerde voor- en nadelen van deze aanpak, evenals voor de geteste alternatieve suggesties.

Referenties

  • Indexen reorganiseren en opnieuw opbouwen
  • sys.dm_db_index_physical_stats

Handig hulpmiddel:

dbForge Index Manager – handige SSMS-invoegtoepassing voor het analyseren van de status van SQL-indexen en het oplossen van problemen met indexfragmentatie.


  1. Primaire MySQL-sleutels

  2. PostgreSQL hoe intervalwaarde '2 dagen' samen te voegen

  3. Bereken de leeftijd in jaren in PostgreSQL

  4. Easysoft Release ODBC-ODBC Bridge voor Windows 10