Index fragmentation in SQL Server is a critical concern that can impact the performance and efficiency of database operations. As data within a database undergoes frequent modifications, such as inserts, updates, and deletes, the logical order of index pages may become misaligned with the physical storage order. This misalignment results in index fragmentation, where the data pages are scattered rather than contiguous, leading to increased I/O operations and degraded query performance. Recognizing and addressing index fragmentation is imperative for maintaining optimal database performance.
This code snippet serves as a powerful and controlled testing ground, enabling database professionals to imitate the fragmentation in full control. The script performs random DML operations on SQL table, measuring the primary key index fragmentation after each set of operations. The loop continues until reaching the desired fragmentation percentage limit.
In my case I execute the script on a standalone SQL Server instance on my laptop; the execution parameters include starting with 100’000 rows in a table and reaching 30% of fragmentation. The parameters can be modified, however the administrator must be careful if the execution do not exceeds the server capabilities. My execution took around 40 seconds.
--1. Creating a table set nocount on drop table if exists __FragmentationTable create table __FragmentationTable ( ID int identity , Text_value nvarchar(100) , Int_value int , Date_value date , constraint PK_ID___FragmentationTable primary key (ID) ) --2. Populating a table with random values declare @numrows int = 1 declare @maxrows int = 100000 --number of rows to be kept accross the executions declare @rand_text nvarchar(100) , @rand_int int , @rand_date date while @numrows <= @maxrows --populating a table with random values begin set @rand_text = CONVERT(nvarchar(100), NEWID()) set @rand_int = FLOOR(RAND() * (@maxrows - 1 + 1)) + 1 set @rand_date = DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 3650), '2000-01-01') insert __FragmentationTable ([Text_value], [Int_value], [Date_value]) values(@rand_text, @rand_int, @rand_date); set @numrows = @numrows + 1; end --3. UPDATING random number of rows IN LOOP declare @loop_count int = 1, @fragmentation_perc decimal(8,5) = 0, @fragmentation_perc_target decimal(8,5) = 30 --percentage of fragmentation to be reached declare @random_updates int declare @random_deletes int declare @row_count int = @maxrows declare @sql nvarchar(max) while @fragmentation_perc <= @fragmentation_perc_target --while loop, until reaching the fragmentation begin set @random_updates = FLOOR(RAND() * (@row_count - 1 + 1)) + 1 --randomised number of updated rows set @random_deletes = FLOOR(RAND() * (@row_count/10 - 1 + 1)) + 1 --randomised number of deleted & inserted rows set @sql = 'DROP TABLE IF EXISTS ##SampleUpdatesTable; ' set @sql = concat(@sql, 'select ID into ##SampleUpdatesTable from __FragmentationTable tablesample(',@random_updates,' rows); ') --interestingly - tablesample is not ideal set @sql = concat(@sql, 'DROP TABLE IF EXISTS ##SampleDeleteTable; ') set @sql = concat(@sql, 'select ID into ##SampleDeleteTable from __FragmentationTable tablesample(',@random_deletes,' rows); ') --interestingly - tablesample is not ideal exec sys.sp_executesql @sql --updates update ft set Text_value = NEWID(), Int_value = ABS(CHECKSUM(NewId())) % 1000000, date_value = DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 3650), '2000-01-01') from __FragmentationTable ft join ##SampleUpdatesTable sut on ft.ID = sut.ID --deletes delete ft from __FragmentationTable ft join ##SampleDeleteTable sut on ft.ID = sut.ID --inserts set @numrows = 1 while @numrows <= @random_deletes begin set @rand_text = CONVERT(nvarchar(100), NEWID()) set @rand_int = FLOOR(RAND() * (@maxrows - 1 + 1)) + 1 set @rand_date = DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 3650), '2000-01-01') insert __FragmentationTable ([Text_value], [Int_value], [Date_value]) values (@rand_text, @rand_int, @rand_date); set @numrows = @numrows + 1; end set @row_count = (select count(*) from __FragmentationTable) --updating row count - not ideal @maxrows --4. Checking the fragmentation select @fragmentation_perc=IDXPS.avg_fragmentation_in_percent --checking fragmentation percentage from sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) IDXPS inner join sys.indexes IDX on IDX.object_id = IDXPS.object_id and IDX.index_id = IDXPS.index_id where IDX.name='PK_ID___FragmentationTable' print concat('Exec no.: ', @loop_count, char(9), ' Updated rows: ', @random_updates, char(9),' Deleted rows: ', @random_deletes, char(9), ' Rows count after that: ', @row_count, char(9), ' Fragmentation %: ', @fragmentation_perc) set @loop_count = @loop_count + 1 end