SQL Server Fragmentation: The Complete Guide for DBAs

SQL Server Fragmentation: The Complete Guide for DBAs – SQLYARD

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


The Three Types of Fragmentation
📊
1. Index Fragmentation
Data and index pages become logically scattered or poorly filled. Affects read performance on spinning disk. Lower impact on SSD.
Fix: ALTER INDEX REORGANIZE or REBUILD
🗄️
2. Database File Growth Fragmentation
MDF and LDF files grow in many small chunks across the volume. Affects all storage types including SSD. Causes I/O scatter at the OS level.
Fix: Pre-size files, use fixed MB autogrowth
💾
3. Storage / OS Fragmentation
File system level disk fragmentation from shrink-grow cycles. Affects HDD more than SSD. Avoided by stable file sizes and no auto shrink.
Fix: Pre-size files, disable auto shrink

What Fragmentation Looks Like
LOW FRAGMENTATION (Good)
1
2
3
4
5
6
7
8
9
10
Pages stored in logical order. Fewer I/O operations.
HIGH FRAGMENTATION (Bad on HDD)
1
7
3
9
2
5
4
8
6
10
Pages scattered. Read head must seek on HDD. SSD impact is lower.

Fragmentation Thresholds and Actions
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

Storage Type Matters
💿 Spinning Disk
Random I/O Slow
Fragmentation impact High
Seek cost High
Reorg threshold 5%+
Rebuild threshold 30%+
SSD / NVMe
Random I/O Fast
Fragmentation impact Low
Seek cost None
Reorg threshold 30%+
Rebuild threshold 60%+

🚨 Never Routinely Run SHRINK
DBCC SHRINKDATABASE / SHRINKFILE causes immediate heavy fragmentation on every index it touches. It undoes all your maintenance work. Pre-size your files correctly instead. Use fixed MB autogrowth (512 MB, 1 GB, or 4 GB). Never use percentage growth.
Fill Factor Reference
80–90
OLTP tables with frequent inserts and updates
95–100
Read-heavy or insert-only tables and DW fact tables

Suggested Maintenance Schedule
Update Statistics Daily or after heavy load
Check Fragmentation Levels Weekly
Reorganize or Rebuild Indexes Weekly (based on actual %)
Check File Growth Events Weekly
Review Slow Queries Weekly / Monthly
Run SHRINK Never routinely

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 ActionSSD / NVMe ActionReasoning
0% to 5%Do NothingDo NothingBelow noise threshold on any storage
5% to 30%REORGANIZEDo NothingScatter matters on HDD. Irrelevant on SSD.
30% to 60%REBUILDConsider REORGANIZEHigh scatter on HDD. Page fullness concern on SSD.
Over 60%REBUILDREBUILDExtreme scatter affects buffer pool efficiency on any storage.
Low page fullness regardless of fragmentation %REBUILD with fill factorREBUILD with fill factorPage 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.

EditionOnline Index RebuildImpact
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


Discover more from SQLYARD

Subscribe to get the latest posts sent to your email.

Leave a Reply

Discover more from SQLYARD

Subscribe now to keep reading and get access to the full archive.

Continue reading