Předmluva
World Wide Web nabízí spoustu informací o defragmentaci indexu SQL Server nebo opětovném sestavení indexu SQL Server. Většina doporučení se však týká databází, které mají minimální dobu načítání (většinou v noci).
A co databáze, které se používají jak pro úpravu dat, tak pro získávání informací 24/7?
V tomto článku poskytnu mechanismus pro automatizaci defragmentace indexu SQL Server implementovaný v databázi používané ve společnosti, pro kterou pracuji. Tento mechanismus umožňuje pravidelnou defragmentaci požadovaných indexů, protože fragmentace indexů probíhá neustále v systému 24/7. K provedení defragmentace indexu jednou denně to často nestačí.
Řešení
Nejprve se podívejme na obecný přístup:
- Vytvoření zobrazení ukazující, které indexy byly fragmentovány, a procento fragmentovaných indexů.
- Vytvoření tabulky pro ukládání výsledků defragmentace indexu.
- Vytvoření uložené procedury pro analýzu a defragmentaci vybraného indexu.
- Vytvoření zobrazení pro zobrazení statistik výsledků defragmentace indexu.
- Vytvoření úlohy v Agentu pro spuštění implementované uložené procedury.
A nyní se podívejme na implementaci:
1. Vytvoření zobrazení ukazujícího, které indexy byly fragmentovány, a procento fragmentovaných indexů:
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 Toto zobrazení zobrazuje pouze indexy s procentem fragmentace větším než 30, tj. indexy, které vyžadují defragmentaci. Zobrazuje pouze indexy, které nejsou haldami, protože ty druhé mohou vést k negativním účinkům, jako je zablokování takové haldy nebo další fragmentace indexu.
Pohled používá důležitý systémový pohled sys.dm_db_index_physical_stats.
2. Vytvoření tabulky pro ukládání výsledků defragmentace indexu:
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 Nejdůležitější na této tabulce je pamatovat na smazání dat (například dat, která jsou starší než 1 měsíc).
Pole tabulky budou srozumitelná od dalšího bodu.
3. Vytvoření uložené procedury pro analýzu a defragmentaci vybraného indexu:
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 ['example@sqldat.com+'] on ['example@sqldat.com+'].['example@sqldat.com+'] 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 ['example@sqldat.com+'].['example@sqldat.com+'] ['example@sqldat.com+']';
execute sp_executesql @SQL_Str;
end
END 4. Vytvoření pohledu pro prohlížení statistik výsledků defragmentace indexu:
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 Toto zobrazení lze použít ke každodennímu informování správců o výsledcích automatizace defragmentace indexu.
5. Vytvoření úlohy v Agentu pro spuštění implementované uložené procedury
Zde musíme vybrat čas experimentálním způsobem. V mém případě jsem někde dostal 5 minut, někde – 1 hodinu.
Tento algoritmus lze rozšířit na několik databází, ale v tomto případě potřebujeme další bod 6:
Shromáždění všech statistik automatizace defragmentace indexu na jednom místě pro následné odeslání správcům.
A nyní bych se rád zastavil u již poskytnutých doporučení pro podporu indexů:
- Současná defragmentace všech indexů během minimálního zatížení databáze je pro systémy s nepřetržitým provozem nepřijatelná, protože indexy jsou neustále fragmentovány a není téměř žádný čas, kdy by databáze zůstala nečinná.
- Reorganizace indexu SQL Serveru – tato operace blokuje tabulku nebo oddíl (v případě rozděleného indexu), což není dobré pro systémy 24/7. Pak je přestavba indexu v režimu reálného času podporována pouze v řešení Enterprise a může také vést k poškození dat.
Tato metoda není optimální, ale dokáže si úspěšně poradit se zajištěním správné defragmentace indexů (nepřesahující 30–40 % fragmentace) pro jejich následné použití optimalizátorem pro plány provádění budov.
Budu vděčný za vaše komentáře s odůvodněnými klady a zápory tohoto přístupu, stejně jako za testované alternativní návrhy.
Odkazy
- Reorganizovat a znovu vytvořit indexy
- sys.dm_db_index_physical_stats
Užitečný nástroj:
dbForge Index Manager – praktický doplněk SSMS pro analýzu stavu indexů SQL a řešení problémů s fragmentací indexů.