Defragmentacja indeksów w MSSQL Server

W tym artykule opiszę swoje doświadczenia z tematem defragmentacji indeksów w MSSQL Server.

Do tej pory, jeżeli pojawiła się konieczność uporządkowania indeksów, korzystałem z polecenia

EXEC sp_MSforeachtable @command1="DBCC DBREINDEX ('?')"

dla wszystkich tabel, albo

DBCC DBREINDEX ('table_name')

dla wybranej tabeli.

Ponieważ zgodnie z informacją na stronie https://msdn.microsoft.com/pl-pl/library/ms181671(v=sql.110).aspx polecenie to będzie usunięte w przyszłych wersjach, Microsoft zaleca użycie polecenia ALTER INDEX.

Dlatego przy okazji zagłębiania się w temat postanowiłem napisać skrypt, który wykona defragmentację indeksów z wykorzystaniem tego polecenia zgodnie z zaleceniami firmy Microsoft.

Pierwszy krok, to sprawdzenie aktualnych wartości fragmentacji indeksów przy użyciu polecenia

sys.dm_db_index_physical_stats

Znając aktualne wartości fragmentacji indeksów możemy określić rodzaj operacji (reorganize lub rebuild), jaki należy wykonać na każdym z indeksów (zgodnie z informacją zawartą tutaj).

  1. jeżeli wartość fragmentacji zawiera się między 5 a 30 – wykonujemy operację reorganize
  2. jeżeli wartość fragmentacji jest większa niż 30 – wykonujemy operację rebuild.

Dodatkowo, przy wykonywaniu defragmentacji indeksów należy wziąć pod uwagę wartość page_count. Zgodnie z informacją m.in. z tej strony defragmentacji należy poddać te indeksy, dla których wartość page_count jest większa niż 1000.

Biorąc pod uwagę powyższe założenia napisałem poniższy skrypt

if (object_id('tempdb.dbo.#tables')) is not null drop table #tables

use database1
go

select 
t4.name as database_name,
t1.database_id, 
t3.name as table_name,
t2.name as index_name,
t2.index_id, t1.avg_fragmentation_in_percent, 
t1.index_depth, t1.page_count
into #tables
from sys.dm_db_index_physical_stats (db_id(),null,null,null,'limited') as t1
join sys.indexes as t2 on t1.index_id=t2.index_id and t1.object_id=t2.object_id
join sys.tables as t3 on t3.object_id = t1.object_id
join sys.databases as t4 on t4.database_id = t1.database_id
where 
isnull(avg_fragmentation_in_percent,0)>0


declare @table_name varchar(100), @index_name varchar(100), @index_id int, @frag numeric(38,15), 
@text varchar(1000), @query_string varchar(1000), @frag_after numeric(38,15), 
@old_table_name varchar(100), @index_depth int, @page_count int
select @old_table_name=''

declare k cursor for
select table_name, index_name,index_id, avg_fragmentation_in_percent, index_depth, page_count 
from #tables 
order by 
table_name

open k
fetch next from k into @table_name, @index_name,@index_id, @frag, @index_depth, @page_count
while @@fetch_status=0 begin
    if @table_name<>@old_table_name begin
        print '['+@table_name+']'
        select @old_table_name = @table_name    
    end
    if @page_count<1000 begin
            print '        '+left('['+@index_name+']'+space(50),50)+' page_count<1000'
    end else begin
        if @frag>5 and @frag<=30 begin
            select @query_string = 'alter index '+@index_name+' on '+@table_name+' reorganize '
            exec (@query_string)
            select @frag_after=avg_fragmentation_in_percent 
            from sys.dm_db_index_physical_stats (db_id(),object_id(@table_name),@index_id,null,'limited') 
            where index_depth=@index_depth
            print '        '+left('['+@index_name+']'+space(50),50)+' reorganize before '+convert(varchar,@frag)+' after '+convert(varchar,@frag_after)
        end else if @frag>30 begin
            select @query_string = 'alter index '+@index_name+' on '+@table_name+' rebuild '
            exec (@query_string)
            select @frag_after=avg_fragmentation_in_percent 
            from sys.dm_db_index_physical_stats (db_id(),object_id(@table_name),@index_id,null,'limited') 
            where index_depth=@index_depth
            print '        '+left('['+@index_name+']'+space(50),50)+' rebuild before '+convert(varchar,@frag)+' after '+convert(varchar,@frag_after)
        end else begin        
            print '        '+left('['+@index_name+']'+space(50),50)+' defrag not required, avg_fragmentation '+convert(varchar,@frag)
        end
    end
    fetch next from k into @table_name, @index_name,@index_id, @frag, @index_depth, @page_count
end
close k
deallocate k
drop table #tables
print 'done'

Poniżej wynik wykonania skryptu dla wybranej tabeli

[table_name]
        [index1]     defrag not required, avg_fragmentation 0.170921321056404
        [index2]     reorganize before 8.669527896995708 after 0.438212094653812
        [index3]     defrag not required, avg_fragmentation 0.880281690140845
        [index4]     rebuild before 33.511450381679389 after 0.000000000000000
        [index5]     defrag not required, avg_fragmentation 0.992720052945070
done