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

Automatisering af indeksdefragmentering i MS SQL Server-database

Forord

World Wide Web tilbyder en masse information om SQL Server-indeksdefragmentering eller SQL Server-indeksgenopbygning. De fleste af anbefalingerne henviser dog til databaser, der har minimal indlæsningstid (for det meste om natten).

Og hvad med databaser, der bruges til både datamodifikation og hentning af information 24/7?

I denne artikel vil jeg give en mekanisme til automatisering af SQL Server-indeksdefragmentering implementeret i en database, der bruges i den virksomhed, jeg arbejder for. Denne mekanisme tillader defragmentering af nødvendige indekser på en regelmæssig basis, da indeksfragmentering finder sted konstant i 24/7-systemet. Ofte er dette ikke nok til at udføre indeksdefragmentering én gang om dagen.

Løsning

Lad os først tage et kig på den generelle tilgang:

  1. Oprettelse af en visning, der viser, hvilke indekser der er blevet fragmenteret, og procentdelen af ​​de fragmenterede indekser.
  2. Oprettelse af en tabel til lagring af indeksdefragmenteringsresultater.
  3. Oprettelse af en lagret procedure til analyse og defragmentering af det valgte indeks.
  4. Oprettelse af en visning til visning af statistik over indeksdefragmenteringsresultaterne.
  5. Oprettelse af en opgave i Agent til at køre den implementerede lagrede procedure.

Og lad os nu tage et kig på implementeringen:

1. Oprettelse af en visning, der viser, hvilke indekser der er blevet fragmenteret og procentdelen af ​​de fragmenterede indekser:

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

Denne visning viser kun indekser med fragmenteringsprocenten større end 30, dvs. indekser, der kræver defragmentering. Den viser kun indekser, der ikke er heaps, da sidstnævnte kan føre til negative effekter, såsom blokering af en sådan heap eller yderligere indeksfragmentering.

Visningen bruger den vigtige systemvisning sys.dm_db_index_physical_stats.

2. Oprettelse af en tabel til lagring af indeksdefragmenteringsresultaterne:

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 vigtigste ved denne tabel er at huske på sletning af data (f.eks. data, der er ældre end 1 måned).

Tabelfelter bliver forståelige fra næste punkt.

3. Oprettelse af en lagret procedure til analyse og defragmentering af det valgte indeks:

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. Oprettelse af en visning til visning af statistik over indeksdefragmenteringsresultaterne:

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

Denne visning kan bruges til at underrette administratorer dagligt om resultaterne af automatiseringen af ​​indeksdefragmentering.

5. Oprettelse af en opgave i Agent til at køre den implementerede lagrede procedure

Her skal vi vælge tiden på en eksperimenterende måde. I mit tilfælde fik jeg et eller andet sted 5 minutter, et eller andet sted – 1 time.

Denne algoritme kan udvides på flere databaser, men i dette tilfælde har vi brug for et ekstra punkt 6:

Samling af al statistik for indeksdefragmenteringsautomatisering på ét sted til efterfølgende afsendelse til administratorer.

Og nu vil jeg gerne dvæle ved de allerede angivne anbefalinger til indeksstøtte:

  1. Samtidig defragmentering af alle indekser under den minimale databasebelastning er uacceptabel for 24/7-systemerne, da indekser konstant fragmenteres, og der næsten ikke er nogen tid, hvor databasen forbliver inaktiv.
  2. SQL Server-indeksomorganisering – denne handling blokerer en tabel eller partition (i tilfælde af et partitioneret indeks), hvilket ikke er godt for 24/7-systemerne. Derefter understøttes indeksgenopbygning i realtidstilstand kun i Enterprise-løsningen og kan også føre til dataskade.

Denne metode er ikke optimal, men den kan med succes klare at sikre, at indekser er korrekt defragmenteret (ikke over 30-40 % af fragmenteringen) til deres efterfølgende brug af optimeringsværktøjet til at bygge udførelsesplaner.

Jeg vil være taknemmelig for dine kommentarer med begrundede fordele og ulemper ved denne tilgang, såvel som for de testede alternative forslag.

Referencer

  • Omorganiser og genopbyg indekser
  • sys.dm_db_index_physical_stats

Nyttigt værktøj:

dbForge Index Manager – praktisk SSMS-tilføjelse til at analysere status for SQL-indekser og løse problemer med indeksfragmentering.


  1. Eksempler på konvertering af 'dato' til 'datetime' i SQL Server (T-SQL)

  2. #1055 - Udtryk af SELECT-listen er ikke i GROUP BY-klausul og indeholder ikke-aggregeret kolonne, dette er inkompatibelt med sql_mode=only_full_group_by

  3. proxysql-admin Alternativer - ClusterControl ProxySQL GUI

  4. Android brugerdefineret kalender og påmindelse