SQL Server Fragmentation: The Complete Guide for DBAs
Fragmentation is one of the most misunderstood topics in SQL Server administration. Most DBAs learn the basics: index fragmentation is bad, rebuild your indexes, problem solved. But that oversimplification leads to maintenance strategies that are either doing too much work or not enough, and often doing the wrong kind of work on the wrong storage type.
The reality is that SQL Server fragmentation is three separate problems with three separate causes and three separate solutions. The thresholds that DBAs have used for years were designed for spinning disk and do not translate directly to SSD and NVMe. And the most damaging fragmentation-related practice in SQL Server administration, running SHRINK, is also the most commonly recommended quick fix on the internet.
This article covers all three types of fragmentation, the correct diagnostic approach, the right thresholds for both spinning disk and flash storage, how to build a maintenance strategy that matches your workload and hardware, and the coding and design decisions that prevent fragmentation before it starts.
SQL Server Fragmentation
Complete Guide · SQLYARD.com
| Fragmentation % | HDD Action | SSD / NVMe Action |
|---|---|---|
| 0% to 5% | Do Nothing | Do Nothing |
| 5% to 30% | Reorganize | Do Nothing |
| 30% to 60% | Rebuild | Consider Reorg |
| Over 60% | Rebuild | Rebuild |
| Small index (<1000 pages) | Usually Skip | Usually Skip |
- Type 1: Index Fragmentation
- Type 2: Database File Growth Fragmentation
- Type 3: Storage and OS Fragmentation
- How to Check Index Fragmentation
- How to Check File Growth Fragmentation
- Statistics: The Hidden Fragmentation Problem
- Thresholds for HDD vs SSD vs NVMe
- REORGANIZE vs REBUILD: When to Use Each
- Fill Factor: Setting It Right
- Why SHRINK Makes Everything Worse
- File Growth Best Practices
1 Type 1: Index Fragmentation Beginner
Index fragmentation occurs when the logical order of pages in an index does not match the physical order on disk, or when index pages are not fully used. SQL Server stores data in 8KB pages organized into extents of 8 pages each. When data is inserted, updated, and deleted over time, pages split, pages become partially empty, and the logical chain of pages in an index becomes scattered across the storage volume.
There are two metrics that matter when evaluating index fragmentation. Average fragmentation percentage measures how out-of-order the pages are logically. Page fullness measures how much space within each page is actually used. An index can have low fragmentation percentage but still perform poorly if pages are only 40% full, because SQL Server must read twice as many pages to retrieve the same amount of data.
The impact of index fragmentation depends heavily on the storage type. On spinning disk, fragmentation forces the read head to seek to scattered locations. Sequential reads are dramatically faster than random reads on HDD, so fragmentation has a real and measurable performance impact. On SSD and NVMe, random reads cost the same as sequential reads because there is no physical read head. The page scatter that fragmentation causes is almost irrelevant for read performance on flash storage. What does still matter on flash is page fullness, because reading more pages than necessary costs I/O regardless of storage type.
The goal is not zero fragmentation. The goal is optimal performance with minimal maintenance overhead. Chasing zero fragmentation on a busy OLTP database running on SSD is doing unnecessary work that increases write I/O, consumes maintenance window time, and provides no measurable benefit.
2 Type 2: Database File Growth Fragmentation Beginner
Database file growth fragmentation occurs when the MDF and LDF files grow in many small increments over time, creating file system level fragmentation of the database files themselves. This is separate from index fragmentation and is often completely overlooked by DBAs focused only on index maintenance.
When a SQL Server database file grows, the operating system allocates new space on the volume. If the growth increment is small (the default of 1MB for many older installations, or the problematic 10% percentage growth setting), hundreds or thousands of small allocation events scatter the file across the volume. Even on SSD, file fragmentation at the OS level creates overhead because the file system must track many separate file extents rather than reading the file as a single contiguous allocation.
This type of fragmentation affects all storage types including SSD and NVMe. It is best prevented rather than fixed after the fact. The fix is to pre-size database files close to their expected working size and use fixed large increment autogrowth settings as a safety net only.
3 Type 3: Storage and OS Fragmentation Beginner
Storage and OS level fragmentation is disk fragmentation at the file system layer, the same type that Windows Disk Defragmenter addresses. It primarily affects spinning disk where physical contiguity of file blocks matters for read performance. On SSD and NVMe it has minimal performance impact because the hardware does not care about physical block locality.
The primary cause of this type of fragmentation in SQL Server environments is the shrink-grow cycle. When SHRINK is run, it releases space back to the OS and the database file shrinks. When the database grows again naturally, the OS reallocates space in whatever fragments are available on the volume. This cycle repeats and creates severe OS-level file fragmentation that is expensive to fix and completely avoidable.
SHRINK is the leading cause of all three types of fragmentation. Running SHRINK causes immediate index fragmentation because it moves pages to compact the file. It causes file growth fragmentation because the file will grow back in small increments. And it causes OS-level storage fragmentation because of the shrink-grow cycle. The only appropriate use of SHRINK is an emergency space recovery operation, never a routine maintenance task.
4 How to Check Index Fragmentation Intermediate
Use sys.dm_db_index_physical_stats to check fragmentation. This DMF has five modes: DEFAULT, NULL, LIMITED, SAMPLED, and DETAILED. For routine maintenance checks, LIMITED or SAMPLED is recommended. DETAILED scans every page and is extremely slow on large databases.
-- Check index fragmentation for all user tables
-- Use LIMITED mode for large databases (faster, uses page headers only)
-- Use SAMPLED for more accurate results on medium databases
-- NEVER use DETAILED on production during business hours
SELECT
OBJECT_SCHEMA_NAME(ips.object_id) AS SchemaName,
OBJECT_NAME(ips.object_id) AS TableName,
i.name AS IndexName,
i.type_desc AS IndexType,
ips.index_type_desc,
ips.partition_number,
ips.avg_fragmentation_in_percent,
ips.page_count,
ips.avg_page_space_used_in_percent AS PageFullness,
ips.record_count,
-- Current compression state
p.data_compression_desc AS Compression
FROM sys.dm_db_index_physical_stats(
DB_ID(), -- current database
NULL, -- all objects
NULL, -- all indexes
NULL, -- all partitions
'LIMITED' -- mode: LIMITED is fastest, safe for production
) ips
JOIN sys.indexes i
ON i.object_id = ips.object_id
AND i.index_id = ips.index_id
JOIN sys.partitions p
ON p.object_id = ips.object_id
AND p.index_id = ips.index_id
AND p.partition_number = ips.partition_number
WHERE ips.avg_fragmentation_in_percent > 5 -- only show fragmented
AND ips.page_count > 1000 -- skip tiny indexes
AND OBJECTPROPERTY(ips.object_id, 'IsUserTable') = 1
ORDER BY ips.avg_fragmentation_in_percent DESC;
-- Summary by fragmentation level
SELECT
CASE
WHEN avg_fragmentation_in_percent BETWEEN 0 AND 5 THEN '0-5% (Healthy)'
WHEN avg_fragmentation_in_percent BETWEEN 5 AND 30 THEN '5-30% (Reorganize candidate)'
WHEN avg_fragmentation_in_percent BETWEEN 30 AND 60 THEN '30-60% (Rebuild candidate)'
ELSE 'Over 60% (Rebuild urgently)'
END AS FragmentationBand,
COUNT(*) AS IndexCount,
AVG(avg_fragmentation_in_percent) AS AvgFragmentation,
SUM(page_count) AS TotalPages
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED')
WHERE OBJECTPROPERTY(object_id, 'IsUserTable') = 1
AND page_count > 1000
GROUP BY
CASE
WHEN avg_fragmentation_in_percent BETWEEN 0 AND 5 THEN '0-5% (Healthy)'
WHEN avg_fragmentation_in_percent BETWEEN 5 AND 30 THEN '5-30% (Reorganize candidate)'
WHEN avg_fragmentation_in_percent BETWEEN 30 AND 60 THEN '30-60% (Rebuild candidate)'
ELSE 'Over 60% (Rebuild urgently)'
END
ORDER BY AVG(avg_fragmentation_in_percent) DESC;
5 How to Check File Growth Fragmentation Intermediate
-- Check current database file sizes and autogrowth settings
SELECT
name AS FileName,
physical_name,
type_desc,
size * 8 / 1024 AS SizeMB,
CASE max_size
WHEN -1 THEN 'Unlimited'
WHEN 0 THEN 'No growth'
ELSE CAST(max_size * 8 / 1024 AS VARCHAR(20)) + ' MB'
END AS MaxSize,
CASE is_percent_growth
WHEN 1 THEN CAST(growth AS VARCHAR(10)) + '% (PROBLEMATIC)'
ELSE CAST(growth * 8 / 1024 AS VARCHAR(10)) + ' MB'
END AS AutoGrowth,
is_percent_growth AS IsPercentGrowth
FROM sys.database_files
ORDER BY type_desc, file_id;
-- Check auto growth events in the last 7 days (default trace)
DECLARE @TracePath NVARCHAR(4000);
SELECT @TracePath = CONVERT(NVARCHAR(4000), path)
FROM sys.traces WHERE is_default = 1;
IF @TracePath IS NOT NULL
BEGIN
SELECT
DB_NAME(t.DatabaseID) AS DatabaseName,
CASE t.EventClass
WHEN 92 THEN 'Data File Auto Grow'
WHEN 93 THEN 'Log File Auto Grow'
END AS EventType,
t.FileName,
t.IntegerData * 8 / 1024 AS GrowthMB,
t.StartTime
FROM sys.fn_trace_gettable(@TracePath, DEFAULT) t
WHERE t.EventClass IN (92, 93)
AND t.StartTime >= DATEADD(DAY, -7, GETDATE())
ORDER BY t.StartTime DESC;
END
-- Instant File Initialization status check
-- IFI dramatically speeds up data file growth and creation
-- Requires "Perform volume maintenance tasks" privilege for SQL Server account
-- Does NOT help log files
SELECT
servicename,
instant_file_initialization_enabled
FROM sys.dm_server_services
WHERE servicename LIKE 'SQL Server%'
AND servicename NOT LIKE '%Agent%'
AND servicename NOT LIKE '%Browser%';
6 Statistics: The Hidden Fragmentation Problem Intermediate
Outdated statistics cause bad query plans independently of fragmentation levels. A table can have zero index fragmentation and still run poorly if the query optimizer is working from statistics that no longer reflect the actual data distribution. This is the most important point in the infographic that DBAs underestimate: bad statistics cause bad plans even when fragmentation is low.
-- Check statistics age and update status
SELECT
OBJECT_NAME(s.object_id) AS TableName,
s.name AS StatsName,
sp.last_updated,
sp.rows,
sp.rows_sampled,
CAST(sp.rows_sampled * 100.0
/ NULLIF(sp.rows, 0) AS DECIMAL(5,1)) AS SamplePct,
sp.modification_counter AS RowModsSinceLastUpdate,
DATEDIFF(DAY, sp.last_updated, GETDATE()) AS DaysSinceUpdate
FROM sys.stats s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
WHERE OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1
ORDER BY DaysSinceUpdate DESC, sp.modification_counter DESC;
-- Confirm auto stats settings
SELECT
name,
is_auto_create_stats_on,
is_auto_update_stats_on,
is_auto_update_stats_async_on
FROM sys.databases
WHERE name = DB_NAME();
-- Microsoft warns that turning AUTO_UPDATE_STATISTICS OFF
-- can cause suboptimal plans and degraded performance.
-- These should both be ON in almost every environment:
ALTER DATABASE YourDB SET AUTO_CREATE_STATISTICS ON;
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS ON;
7 Thresholds for HDD vs SSD vs NVMe Intermediate
The traditional fragmentation thresholds (5% reorganize, 30% rebuild) come from Microsoft’s Books Online documentation and were written primarily in the context of spinning disk. Applying them unchanged to SSD and NVMe environments leads to unnecessary maintenance work.
Why Traditional Thresholds Exist
On spinning disk, fragmented indexes mean the read head must physically seek to non-contiguous locations. A fragmented 10GB index on HDD genuinely causes measurable query slowdowns because seeks take 5 to 15 milliseconds each and a large scattered scan can trigger thousands of them. The 5% and 30% thresholds reflect the point where that physical I/O scatter costs more than the maintenance overhead of fixing it.
Why SSD Changes the Equation
On SSD and NVMe there is no read head and no seek time. Random I/O costs the same as sequential I/O because access is electronic rather than mechanical. Microsoft’s own documentation states that for solid state drives, fragmentation has minimal impact on query performance. Running aggressive index rebuilds on SSD to fix fragmentation that is not causing performance issues generates unnecessary write I/O that adds wear to the drive without improving query performance.
What Still Matters on Flash Storage
- Page fullness matters everywhere. If pages are only 50% full, SQL Server reads twice as many pages to return the same data. This is true on SSD and HDD. An index with low fragmentation but low page fullness is still an I/O problem.
- File growth fragmentation matters everywhere. OS-level file fragmentation and many small autogrowth events still affect SSD performance and create unnecessary overhead.
- Write amplification is a real concern on SSD. Frequent index rebuilds on SSD generate significant write I/O. SSD drives have finite write endurance. Unnecessary rebuilds consume that endurance without delivering performance benefit.
- Very high fragmentation (60%+) still warrants attention on SSD because extreme page scatter affects the buffer pool efficiency. Pages that are scattered across many extents are less likely to benefit from read-ahead operations.
| Fragmentation % | HDD Action | SSD / NVMe Action | Reasoning |
|---|---|---|---|
| 0% to 5% | Do Nothing | Do Nothing | Below noise threshold on any storage |
| 5% to 30% | REORGANIZE | Do Nothing | Scatter matters on HDD. Irrelevant on SSD. |
| 30% to 60% | REBUILD | Consider REORGANIZE | High scatter on HDD. Page fullness concern on SSD. |
| Over 60% | REBUILD | REBUILD | Extreme scatter affects buffer pool efficiency on any storage. |
| Low page fullness regardless of fragmentation % | REBUILD with fill factor | REBUILD with fill factor | Page fullness affects logical reads on all storage types. |
Check your storage type before setting thresholds. A maintenance script with hardcoded 5% and 30% thresholds on an all-NVMe storage array is doing potentially three times more work than necessary. Query sys.dm_io_virtual_file_stats and review your storage configuration before deciding on thresholds.
8 REORGANIZE vs REBUILD: When to Use Each Intermediate
REORGANIZE and REBUILD solve the same problem through different mechanisms with different costs and different availability implications.
ALTER INDEX REORGANIZE
Reorganize defragments the leaf level of the index by physically reordering the leaf-level pages to match the logical order. It is an online operation, always. It compacts pages to fill factor. It is interruptible without leaving the index in a bad state. It does not update statistics.
-- Reorganize a specific index
ALTER INDEX IX_YourIndex ON dbo.YourTable REORGANIZE;
-- Reorganize all indexes on a table
ALTER INDEX ALL ON dbo.YourTable REORGANIZE;
-- Reorganize with LOB compaction (for tables with LOB columns)
ALTER INDEX IX_YourIndex ON dbo.YourTable
REORGANIZE WITH (LOB_COMPACTION = ON);
ALTER INDEX REBUILD
Rebuild drops and recreates the index. It fixes all fragmentation, resets page fullness to fill factor, and updates statistics with a full scan. In Enterprise Edition with ONLINE = ON it can run while the table is accessible. In Standard Edition ONLINE rebuild is not supported for most index types, meaning the table is locked during the rebuild.
-- Rebuild a specific index (offline - table locked)
ALTER INDEX IX_YourIndex ON dbo.YourTable REBUILD;
-- Rebuild with fill factor setting
ALTER INDEX IX_YourIndex ON dbo.YourTable
REBUILD WITH (FILLFACTOR = 85);
-- Rebuild online (Enterprise Edition only)
ALTER INDEX IX_YourIndex ON dbo.YourTable
REBUILD WITH (ONLINE = ON);
-- Rebuild specific partition only (partitioned tables)
ALTER INDEX IX_YourIndex ON dbo.YourTable
REBUILD PARTITION = 3 WITH (FILLFACTOR = 90);
-- Generate maintenance commands for all fragmented indexes
-- Adapt thresholds to your storage type
SELECT
CASE
WHEN avg_fragmentation_in_percent > 30
THEN 'ALTER INDEX [' + i.name + '] ON [' +
OBJECT_SCHEMA_NAME(i.object_id) + '].[' +
OBJECT_NAME(i.object_id) + '] REBUILD WITH (ONLINE = ON);'
ELSE 'ALTER INDEX [' + i.name + '] ON [' +
OBJECT_SCHEMA_NAME(i.object_id) + '].[' +
OBJECT_NAME(i.object_id) + '] REORGANIZE;'
END AS MaintenanceCommand,
avg_fragmentation_in_percent,
page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i
ON i.object_id = ips.object_id
AND i.index_id = ips.index_id
WHERE avg_fragmentation_in_percent > 5
AND page_count > 1000
AND i.name IS NOT NULL
ORDER BY avg_fragmentation_in_percent DESC;
9 Fill Factor: Setting It Right Intermediate
Fill factor determines what percentage of each index page is filled when the index is built or rebuilt. A fill factor of 80 means 80% of each page is filled with data and 20% is left empty for future inserts and updates. This reserved space delays page splits, which are the primary cause of fragmentation in busy OLTP tables.
-- Check current fill factor settings per index
SELECT
OBJECT_NAME(i.object_id) AS TableName,
i.name AS IndexName,
i.type_desc,
i.fill_factor,
CASE i.fill_factor
WHEN 0 THEN 'Using server default'
WHEN 100 THEN 'Full pages (insert-only tables)'
ELSE CAST(i.fill_factor AS VARCHAR(5)) + '% fill'
END AS FillFactorDesc
FROM sys.indexes i
WHERE OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
AND i.index_id > 0
ORDER BY OBJECT_NAME(i.object_id), i.index_id;
-- Check server-level default fill factor
SELECT name, value_in_use
FROM sys.configurations
WHERE name = 'fill factor (%)';
-- Rebuild with specific fill factor
-- 85 is a reasonable starting point for busy OLTP tables on HDD
-- For SSD, higher fill factor (90-95) reduces page count without much fragmentation cost
ALTER INDEX IX_YourIndex ON dbo.YourTable
REBUILD WITH (FILLFACTOR = 85, ONLINE = ON);
-- For tables with GUID/NEWID() clustered keys
-- These fragment heavily due to random inserts
-- Lower fill factor (70-80) or switch to NEWSEQUENTIALID()
CREATE INDEX IX_Table_GuidColumn
ON dbo.YourTable (GuidColumn)
WITH (FILLFACTOR = 80);
Fill factor and storage type. On SSD you can safely use a higher fill factor (90 to 95) compared to HDD (80 to 90) because the random I/O cost of page splits is lower on flash storage. A higher fill factor means more data per page which means fewer total pages to read for any given query, reducing logical reads regardless of storage type.
10 Why SHRINK Makes Everything Worse Beginner
DBCC SHRINKDATABASE and DBCC SHRINKFILE are the most damaging commands available in SQL Server when run routinely. They cause fragmentation, they cause page splits, they invalidate pages in the buffer pool, and the space they recover is almost always consumed again within days or weeks as the database returns to its natural size. Every DBA who has run SHRINK to recover disk space has then watched the database grow back to its original size within a short period.
-- DO NOT run these routinely:
-- DBCC SHRINKDATABASE(YourDB);
-- DBCC SHRINKFILE(YourDB_Data, TargetSizeMB);
-- What happens when you shrink:
-- 1. Pages are moved from the end of the file to fill earlier gaps
-- 2. Every moved page causes fragmentation in its index
-- 3. Index fragmentation immediately increases from potentially 5% to 80%+
-- 4. The database grows back as data is inserted
-- 5. File growth fragmentation occurs as the file expands in small increments
-- 6. Your next index maintenance job has to undo all the damage
-- INSTEAD: Fix the real problem
-- Pre-size files at creation to avoid growth events
ALTER DATABASE YourDB
MODIFY FILE (
NAME = YourDB_Data,
SIZE = 50GB, -- set to expected working size
FILEGROWTH = 4GB -- large fixed increment, not percentage
);
ALTER DATABASE YourDB
MODIFY FILE (
NAME = YourDB_Log,
SIZE = 10GB,
FILEGROWTH = 1GB
);
-- Never use percentage autogrowth:
-- FILEGROWTH = 10% BAD: grows by increasing amounts over time
-- FILEGROWTH = 4GB GOOD: predictable, fewer growth events
11 File Growth Best Practices Intermediate
- Pre-size databases at creation. Set the initial file size close to what you expect the database to need in its first year. Autogrowth should fire rarely, not constantly.
- Use fixed MB autogrowth, never percentage. 512MB, 1GB, or 4GB depending on database size. Percentage growth creates increasingly large growth events as the database grows which is unpredictable and can cause long stalls.
- Enable Instant File Initialization for data files. IFI allows SQL Server to skip zeroing out new data file space, making growth operations nearly instantaneous. It requires granting the SQL Server service account the “Perform volume maintenance tasks” Windows privilege. It does not apply to log files.
- Monitor growth events weekly. If auto growth is firing frequently, pre-size the files larger rather than accepting the growth overhead.
- Separate data, log, and TempDB onto different volumes. This is not just a performance recommendation. It prevents one file from consuming space that another file needs to grow into.
12 Application Design That Prevents Fragmentation Beginner
A significant amount of fragmentation comes from application and schema design decisions rather than maintenance gaps. The right design prevents fragmentation from accumulating as fast. The wrong design makes it impossible to keep up with through maintenance alone.
Clustered Key Choice
Using NEWID() as a clustered index key generates random GUIDs that cause page splits on almost every insert. Every new row goes into a random position in the index rather than at the end, forcing page splits continuously. Use NEWSEQUENTIALID() instead, or an identity integer, both of which insert at the logical end of the index and generate far less fragmentation.
-- BAD: Random GUID causes fragmentation on every insert
CREATE TABLE dbo.Orders (
OrderID UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY, -- random inserts everywhere
OrderDate DATE NOT NULL,
CustomerID INT NOT NULL
);
-- BETTER: Sequential GUID
CREATE TABLE dbo.Orders (
OrderID UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() PRIMARY KEY, -- sequential
OrderDate DATE NOT NULL,
CustomerID INT NOT NULL
);
-- BEST for most OLTP: Identity integer
CREATE TABLE dbo.Orders (
OrderID INT IDENTITY(1,1) PRIMARY KEY, -- always appends, minimal fragmentation
OrderDate DATE NOT NULL,
CustomerID INT NOT NULL
);
-- Apply fill factor at index creation for tables that update frequently
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID
ON dbo.Orders (CustomerID)
INCLUDE (OrderDate)
WITH (FILLFACTOR = 85); -- leave 15% free for updates
Too Many Indexes
Every non-clustered index on a table must be maintained on every INSERT, UPDATE, and DELETE. Tables with 15 or 20 indexes generate fragmentation 15 to 20 times faster than a table with 3 or 4 well-chosen indexes. Review index usage with sys.dm_db_index_usage_stats and drop indexes that are never used by queries.
13 Building a Maintenance Strategy Intermediate
An adaptive maintenance strategy checks actual fragmentation levels and acts accordingly rather than rebuilding everything on a fixed schedule regardless of need. Ola Hallengren’s SQL Server Maintenance Solution is the community standard for this and is free, well documented, and trusted in production environments worldwide.
-- Adaptive index maintenance script
-- Adjust thresholds based on your storage type
-- SSD: raise Reorganize to 30%, Rebuild to 60%
-- HDD: keep Reorganize at 5%, Rebuild at 30%
DECLARE @IsFlashStorage BIT = 1; -- Set to 1 for SSD/NVMe, 0 for HDD
DECLARE @ReorgThreshold DECIMAL(5,2) = CASE @IsFlashStorage WHEN 1 THEN 30.0 ELSE 5.0 END;
DECLARE @RebuildThreshold DECIMAL(5,2) = CASE @IsFlashStorage WHEN 1 THEN 60.0 ELSE 30.0 END;
DECLARE @MinPageCount INT = 1000;
DECLARE @SQL NVARCHAR(MAX);
DECLARE @Schema NVARCHAR(128);
DECLARE @Table NVARCHAR(128);
DECLARE @Index NVARCHAR(128);
DECLARE @Frag DECIMAL(5,2);
DECLARE maintenance_cursor CURSOR FAST_FORWARD FOR
SELECT
OBJECT_SCHEMA_NAME(ips.object_id),
OBJECT_NAME(ips.object_id),
i.name,
ips.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i
ON i.object_id = ips.object_id
AND i.index_id = ips.index_id
WHERE ips.avg_fragmentation_in_percent >= @ReorgThreshold
AND ips.page_count >= @MinPageCount
AND i.name IS NOT NULL
AND OBJECTPROPERTY(ips.object_id, 'IsUserTable') = 1
ORDER BY ips.avg_fragmentation_in_percent DESC;
OPEN maintenance_cursor;
FETCH NEXT FROM maintenance_cursor INTO @Schema, @Table, @Index, @Frag;
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Frag >= @RebuildThreshold
SET @SQL = 'ALTER INDEX [' + @Index + '] ON [' + @Schema + '].[' + @Table + '] REBUILD;'
ELSE
SET @SQL = 'ALTER INDEX [' + @Index + '] ON [' + @Schema + '].[' + @Table + '] REORGANIZE;'
PRINT @SQL;
-- EXEC sp_executesql @SQL; -- Uncomment to actually execute
FETCH NEXT FROM maintenance_cursor INTO @Schema, @Table, @Index, @Frag;
END
CLOSE maintenance_cursor;
DEALLOCATE maintenance_cursor;
14 SQL Server Edition Considerations Beginner
The SQL Server edition you are running directly affects which maintenance strategies are available and what fragmentation remediation is possible without downtime.
| Edition | Online Index Rebuild | Impact |
|---|---|---|
| Express | Not available | Limited to offline rebuilds. Resource limits (1 CPU, 1GB RAM) also constrain maintenance speed. |
| Standard | Very limited (SQL 2016 SP1+) | Most index rebuilds are offline and lock the table. Plan maintenance during low-traffic windows. REORGANIZE is always online. |
| Enterprise | Full ONLINE = ON support | Online index rebuilds allow maintenance while the table is accessible. Resumable index rebuilds available in SQL Server 2017+. |
Standard Edition strategy: Since offline rebuilds lock the table, use REORGANIZE more aggressively on Standard Edition instead of rebuilds. REORGANIZE is always online, always interruptible, and for moderate fragmentation levels it is sufficient. Save REBUILD for very high fragmentation levels during confirmed off-hours windows.
References
- Microsoft Docs: Reorganize and Rebuild Indexes
- Microsoft Docs: sys.dm_db_index_physical_stats
- Microsoft Docs: Specify Fill Factor for an Index
- Microsoft Docs: Database Instant File Initialization
- Microsoft Docs: DBCC SHRINKDATABASE
- Ola Hallengren: SQL Server Index and Statistics Maintenance Solution
- Brent Ozar: How to Think About SQL Server Index Fragmentation
- Paul Randal (SQLskills): Fragmentation and the Fill Factor
- SQLYARD: SQL Server Index Tuning Guide
- SQLYARD: SQL Server Performance Tuning Complete Guide
- SQLYARD: SQL Server Compression and Partitioning Guide
Discover more from SQLYARD
Subscribe to get the latest posts sent to your email.


