DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Optimizing SQL Server Performance With AI: Automating Query Optimization and Predictive Maintenance
  • Essential Techniques for Performance Tuning in Snowflake
  • Essential Relational Database Structures and SQL Tuning Techniques
  • Common Mistakes to Avoid When Writing SQL Code

Trending

  • Start Coding With Google Cloud Workstations
  • Measuring the Impact of AI on Software Engineering Productivity
  • How Large Tech Companies Architect Resilient Systems for Millions of Users
  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  1. DZone
  2. Data Engineering
  3. Databases
  4. SQL Server Index Optimization Strategies: Best Practices with Ola Hallengren’s Scripts

SQL Server Index Optimization Strategies: Best Practices with Ola Hallengren’s Scripts

This guide covers the effective implementation of Ola Hallengren's SQL Server Maintenance Solution for index optimization, especially in Availability Group environments.

By 
arvind toorpu user avatar
arvind toorpu
DZone Core CORE ·
Apr. 25, 25 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
5.8K Views

Join the DZone community and get the full member experience.

Join For Free

Index maintenance is a critical component of database administration as it helps ensure the ongoing efficiency and performance of a Structured Query Language (SQL) Server environment. Over time, as data is added, updated, and deleted, index fragmentation can occur, where the logical and physical ordering of index pages becomes misaligned. This fragmentation can lead to increased disk I/O, decreased query performance, and overall system inefficiency. Running index maintenance jobs, such as those provided by the Ola Hallengren SQL Server Maintenance Solution, allows DBAs to proactively address this fragmentation and optimize the indexes for better performance.

By regularly monitoring index fragmentation levels and executing maintenance operations like index reorganizations and rebuilds, DBAs can keep their databases running at peak efficiency. This is especially important for large, mission-critical databases, where any degradation in performance can have a significant business impact. Maintaining optimal index health helps ensure fast, reliable data access, reduced resource consumption, and an overall improvement in the user experience. Consequently, implementing a well-designed index maintenance strategy is a crucial responsibility for any DBA managing a complex SQL Server environment.

Ola Hallengren's SQL Server Maintenance Solution

The SQL Server Maintenance Solution, developed by Ola Hallengren, is a widely adopted and trusted set of scripts used by database administrators worldwide. This comprehensive solution automates various maintenance tasks, including index optimization, database integrity checks, and statistics updates. Ola's scripts have become the industry standard for proactive database maintenance.

The IndexOptimize procedure from the Maintenance Solution provides extensive customization and configuration options to tailor the index maintenance process for specific environments and requirements. Many database administrators rely on these scripts as the foundation for their index management strategy, as they offer a robust and efficient way to keep indexes in an optimal state.

You can download the latest SQL Server Maintenance Solution version from Ola Hallengren's website. The scripts are released under the MIT License, allowing users to freely use, modify, and distribute them as needed.

Core IndexOptimize Parameters and Their Impact

 The `IndexOptimize` stored procedure provides extensive customization through numerous parameters. Understanding these is critical for effective implementation:        

Essential Parameters                             

ParameterDescriptionImpact
`@Databases`Target databasesControls scope of operation
`@FragmentationLow`Action for low fragmentationTypically NULL (no action)
`@FragmentationMedium`Action for medium fragmentationUsually REORGANIZE
`@FragmentationHigh`Action for high fragmentationREBUILD or REORGANIZE
`@FragmentationLevel1`Low/medium threshold (%)Typically 5-15%
`@FragmentationLevel2`Medium/high threshold (%)Typically 30-40%
`@PageCountLevel`Minimum index size to processExcludes small indexes
`@SortInTempdb`Use tempdb for sortingReduces production database I/O
`@MaxDOP`Degree of parallelismControls CPU utilization
`@FillFactor`Index fill factorControls free space in pages
`@PadIndex`Apply fill factor to non-leaf levelsAffects overall index size
`@LOBCompaction`Compact LOB dataReduces storage for LOB columns
`@UpdateStatistics`Update statistics after rebuild'ALL', 'COLUMNS', 'INDEX', NULL
`@OnlyModifiedStatistics`Only update changed statisticsReduces unnecessary updates
`@TimeLimit`Maximum execution time (seconds)Prevents runaway jobs
`@Delay`Pause between operations (seconds)Reduces continuous resource pressure
`@Indexes`Specific indexes to maintainAllows targeted maintenance
`@MinNumberOfPages`Minimum size thresholdAlternative to PageCountLevel
`@MaxNumberOfPages`Maximum size thresholdLimits operation to smaller indexes
`@LockTimeout`Lock timeout (seconds)Prevents blocking
`@LogToTable`Log operations to tableEnables tracking/troubleshooting
ParameterDescriptionRecommended Setting
`@AvailabilityGroups`Target specific AGsLimit scope when needed
`@AvailabilityGroupReplicas`Target specific replicas'PRIMARY' to limit AG impact
`@AvailabilityGroupDatabases`Target specific databasesFocus on critical databases

                  

Availability Group-Specific Parameters

ParameterDescriptionRecommended Setting
`@AvailabilityGroups`Target specific AGsLimit scope when needed
`@AvailabilityGroupReplicas`Target specific replicas'PRIMARY' to limit AG impact
`@AvailabilityGroupDatabases`Target specific databasesFocus on critical databases

                  

Implementation Strategies by Index Size

Large Indexes (>10GB)

EXECUTE dbo.IndexOptimize
    @Databases = 'PRODUCTION_DB',
    @FragmentationLow = NULL,
    @FragmentationMedium = 'INDEX_REORGANIZE',
    @FragmentationHigh = 'INDEX_REORGANIZE,INDEX_REBUILD_ONLINE',
    @FragmentationLevel1 = 15,
    @FragmentationLevel2 = 40,
    @PageCountLevel = 10000, -- Only process substantial indexes
    @MaxDOP = 4,            -- Limit CPU utilization
    @TimeLimit = 7200,      -- 2-hour limit per operation
    @Delay = '00:00:45',    -- 45-second pause between operations
    @SortInTempdb = 'Y',    -- Reduce database file I/O
    @MaxNumberOfPages = NULL, -- No upper limit
    @MinNumberOfPages = 10000,
    @LockTimeout = 300,     -- 5-minute lock timeout
    @LogToTable = 'Y',
    @Execute = 'Y';

 Special considerations:            

  • Prefer REORGANIZE for large indexes to minimize transaction log growth
  • Use REBUILD selectively when reorganize is insufficient
  • Implement larger `@Delay`to allow transaction log processing
  • Schedule during low-activity periods
  • Consider smaller batches using `@Indexes` parameter

Medium Indexes (1GB-10GB)

EXECUTE dbo.IndexOptimize
    @Databases = 'PRODUCTION_DB',
    @FragmentationLow = NULL,
    @FragmentationMedium = 'INDEX_REORGANIZE',
    @FragmentationHigh = 'INDEX_REBUILD_ONLINE',
    @FragmentationLevel1 = 10,
    @FragmentationLevel2 = 30,
    @PageCountLevel = 1000,
    @MaxDOP = 2,
    @TimeLimit = 3600,      -- 1-hour limit
    @Delay = '00:00:20',    -- 20-second pause
    @SortInTempdb = 'Y',
    @MinNumberOfPages = 1000,
    @MaxNumberOfPages = 10000,
    @LockTimeout = 180,     -- 3-minute lock timeout
    @LogToTable = 'Y',
    @Execute = 'Y';

Special considerations:      

  • Balance between REORGANIZE and REBUILD operations
  • Moderate `@Delay` value to manage resource impact
  • Can run more frequently than large index maintenance

Small Indexes (<1GB)

EXECUTE dbo.IndexOptimize
    @Databases = 'PRODUCTION_DB',
    @FragmentationLow = NULL,
    @FragmentationMedium = 'INDEX_REORGANIZE',
    @FragmentationHigh = 'INDEX_REBUILD_ONLINE',
    @FragmentationLevel1 = 5,
    @FragmentationLevel2 = 30,
    @PageCountLevel = 100,
    @MaxDOP = 0,            -- Use server default
    @TimeLimit = 1800,      -- 30-minute limit
    @Delay = '00:00:05',    -- 5-second pause
    @SortInTempdb = 'Y',
    @MaxNumberOfPages = 1000,
    @MinNumberOfPages = 100,
    @LockTimeout = 60,      -- 1-minute lock timeout
    @LogToTable = 'Y',
    @Execute = 'Y';

Special considerations: 

  • Can be more aggressive with rebuild operations.
  • Minimal `@Delay` needed between operations.
  • Can run during regular business hours with minimal impact.

Availability Group-Specific Configurations

Environment: Large, mission-critical OLTP database with multiple replicas in an Availability Group (AG) configured for synchronous commit.

Maintenance Objectives:

  • Minimize impact on production workload and log shipping.
  • Avoid exhausting storage resources due to log growth.
  • Ensure high availability and minimal downtime.

Synchronous AG Environment

EXECUTE dbo.IndexOptimize
    @Databases = 'PRODUCTION_DB',
    @FragmentationLow = NULL,
    @FragmentationMedium = 'INDEX_REORGANIZE',
    @FragmentationHigh = 'INDEX_REORGANIZE',  -- Avoid rebuilds in sync AGs
    @FragmentationLevel1 = 15,
    @FragmentationLevel2 = 40,
    @PageCountLevel = 5000,
    @MaxDOP = 2,
    @TimeLimit = 3600,
    @Delay = '00:01:00',    -- Longer delay for sync replicas
    @AvailabilityGroupReplicas = 'PRIMARY',
    @LockTimeout = 300,
    @LogToTable = 'Y',
    @Execute = 'Y';

Synchronous AG considerations:

  • Minimize rebuilds - Transaction logs must be synchronized before the operation completes.
  • Implement longer delays between operations to allow synchronization.
  • Monitor replica lag and suspend jobs if lag exceeds thresholds.
  • Increase log backup frequency during maintenance windows.
  • Split maintenance across multiple days for very large environments.

Asynchronous AG Environment

Environment: Large, multi-terabyte data warehouse database with asynchronous AG replicas.

Maintenance Objectives:

  • Perform comprehensive index and statistics maintenance
  • Minimize the impact on the reporting workload during the maintenance window
  • Ensure optimal performance for the upcoming quarter
EXECUTE dbo.IndexOptimize
    @Databases = 'PRODUCTION_DB',
    @FragmentationLow = NULL,
    @FragmentationMedium = 'INDEX_REORGANIZE',
    @FragmentationHigh = 'INDEX_REBUILD_ONLINE',  -- Rebuilds more acceptable
    @FragmentationLevel1 = 10,
    @FragmentationLevel2 = 30,
    @PageCountLevel = 2000,
    @MaxDOP = 4,
    @TimeLimit = 5400,
    @Delay = '00:00:30',    -- Moderate delay
    @AvailabilityGroupReplicas = 'PRIMARY',
    @LockTimeout = 240,
    @LogToTable = 'Y',
    @Execute = 'Y';

Asynchronous AG considerations:

  • More liberal with rebuilds - operations don't wait for secondary synchronization.
  • Still monitor send queue to prevent overwhelming secondaries.
  • Consider network bandwidth and adjust `@Delay` accordingly.
  • Implement send queue size alerts during maintenance.

Preventing Storage and IOPS Pressure

Pre-Maintenance Preparation

Expand transaction log files proactively:

ALTER DATABASE [YourDatabase] MODIFY FILE (NAME = LogFileName, SIZE = ExpandedSizeInMB);

Configure TempDB properly:

-- Verify TempDB configuration
   SELECT name, size/128.0 AS [Size_MB]
   FROM tempdb.sys.database_files;

Implement pre-maintenance checks:

-- Create helper procedure to validate environment readiness
   CREATE PROCEDURE dbo.ValidateMaintenanceReadiness
   AS
   BEGIN
       DECLARE @IssuesFound BIT = 0;
       
       -- Check log space
       IF EXISTS (
           SELECT 1 FROM sys.databases d
           CROSS APPLY sys.dm_db_log_space_usage() l
           WHERE d.database_id = DB_ID()
           AND l.log_space_used_percent > 30
       )
       BEGIN
           RAISERROR('Log usage exceeds 30%. Backup logs before proceeding.', 16, 1);
           SET @IssuesFound = 1;
       END
       
       -- Check AG health
       IF EXISTS (
           SELECT 1 FROM sys.dm_hadr_availability_replica_states ars
           JOIN sys.availability_replicas ar ON ars.replica_id = ar.replica_id
           WHERE ars.is_local = 0
           AND ars.synchronization_health <> 2 -- Not HEALTHY
       )
       BEGIN
           RAISERROR('Availability Group replicas not in healthy state.', 16, 1);
           SET @IssuesFound = 1;
       END
       
       RETURN @IssuesFound;
   END;
   GO

             

Operational Techniques

Implement dynamic index selection based on business impact:

-- Create index priority categories
   CREATE TABLE dbo.IndexMaintenancePriority (
       SchemaName NVARCHAR(128),
       TableName NVARCHAR(128),
       IndexName NVARCHAR(128),
       Priority INT, -- 1=High, 2=Medium, 3=Low
       MaintenanceDay TINYINT -- Day of week (1-7)
   );
   
   -- Use with dynamic execution
   DECLARE @IndexList NVARCHAR(MAX);
   SELECT @IndexList = STRING_AGG(CONCAT(DB_NAME(), '.', SchemaName, '.', TableName, '.', IndexName), ',')
   FROM dbo.IndexMaintenancePriority
   WHERE Priority = 1 AND MaintenanceDay = DATEPART(WEEKDAY, GETDATE());
   
   EXEC dbo.IndexOptimize
       @Databases = 'PRODUCTION_DB',
       @Indexes = @IndexList,
       -- other parameters


Implement I/O throttling techniques:  

  • Use Resource Governor to limit I/O (SQL Server Enterprise).
  • Set lower `@MaxDOP` values during business hours.
  • Implement longer `@Delay` values during peak periods.

Database-level I/O tuning:

-- Consider trace flag 1117 for uniform file growth
   DBCC TRACEON(1117, -1);
   
   -- Consider trace flag 1118 for reducing SGAM contention
   DBCC TRACEON(1118, -1);
   
   -- For SQL Server 2016+, use proper tempdb configuration
   ALTER DATABASE [tempdb] MODIFY FILE (NAME = 'tempdev', SIZE = 8GB);

             

Advanced Scheduling Strategies     

Workload-Aware Batching

-- Create helper procedure for smart batching
CREATE PROCEDURE dbo.ExecuteIndexMaintenanceBatch
    @BatchSize INT = 5,
    @MaxRuntime INT = 7200  -- 2 hours in seconds
AS
BEGIN
    DECLARE @StartTime DATETIME = GETDATE();
    DECLARE @EndTime DATETIME = DATEADD(SECOND, @MaxRuntime, @StartTime);
    DECLARE @CurrentTime DATETIME;
    DECLARE @IndexBatch NVARCHAR(MAX);
    
    WHILE (1=1)
    BEGIN
        SET @CurrentTime = GETDATE();
        IF @CurrentTime > @EndTime BREAK;
        
        -- Get next batch of indexes based on priority and fragmentation
        SELECT TOP (@BatchSize) @IndexBatch = STRING_AGG(CONCAT(DB_NAME(), '.', s.name, '.', t.name, '.', i.name), ',')
        FROM sys.indexes i
        JOIN sys.tables t ON i.object_id = t.object_id
        JOIN sys.schemas s ON t.schema_id = s.schema_id
        JOIN sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ps 
            ON ps.object_id = i.object_id AND ps.index_id = i.index_id
        WHERE i.type_desc = 'NONCLUSTERED'
        AND ps.avg_fragmentation_in_percent > 30
        AND ps.page_count > 1000
        AND NOT EXISTS (
            -- Skip indexes we've already processed
            SELECT 1 FROM dbo.CommandLog
            WHERE DatabaseName = DB_NAME()
            AND SchemaName = s.name
            AND ObjectName = t.name
            AND IndexName = i.name
            AND StartTime > DATEADD(DAY, -7, GETDATE())
        )
        ORDER BY ps.avg_fragmentation_in_percent DESC;
        
        IF @IndexBatch IS NULL BREAK;  -- No more work to do
        
        -- Execute maintenance for this batch
        EXEC dbo.IndexOptimize
            @Databases = DB_NAME(),
            @Indexes = @IndexBatch,
            @FragmentationLow = NULL,
            @FragmentationMedium = 'INDEX_REORGANIZE',
            @FragmentationHigh = 'INDEX_REORGANIZE',
            @FragmentationLevel1 = 10,
            @FragmentationLevel2 = 30,
            @MaxDOP = 2,
            @TimeLimit = 1800,  -- 30 minutes per batch
            @Delay = '00:00:30',
            @LogToTable = 'Y',
            @Execute = 'Y';
            
        -- Pause between batches
        WAITFOR DELAY '00:01:00';
    END
END;
GO

             

Monitoring Framework

-- Create monitoring stored procedure
CREATE PROCEDURE dbo.MonitorIndexMaintenance
AS
BEGIN
    -- Check transaction log usage
    SELECT DB_NAME(database_id) AS DatabaseName,
           log_space_in_use_percentage
    FROM sys.dm_db_log_space_usage
    WHERE log_space_in_use_percentage > 50;
    
    -- Check AG send queue size
    SELECT ar.replica_server_name,
           drs.database_name,
           drs.log_send_queue_size,
           drs.log_send_rate,
           drs.redo_queue_size,
           drs.redo_rate
    FROM sys.dm_hadr_database_replica_states drs
    JOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id
    WHERE drs.log_send_queue_size > 10000
    OR drs.redo_queue_size > 10000;
    
    -- Check ongoing index operations
    SELECT r.session_id,
           r.command,
           r.status,
           r.wait_type,
           r.wait_time,
           OBJECT_NAME(p.object_id) AS ObjectName,
           p.index_id,
           i.name AS IndexName
    FROM sys.dm_exec_requests r
    CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
    LEFT JOIN sys.partitions p ON p.hobt_id = r.statement_id
    LEFT JOIN sys.indexes i ON i.object_id = p.object_id AND i.index_id = p.index_id
    WHERE t.text LIKE '%INDEX_REBUILD%' OR t.text LIKE '%INDEX_REORGANIZE%';
END;
GO

             

Best Practices Summary

For synchronous AG environments:  

  • Prioritize REORGANIZE over REBUILD, especially for large indexes.
  • Implement longer delays between operations (45-90 seconds).  
  • Schedule maintenance during the least active periods.
  • Consider partitioning very large tables for incremental maintenance.

For asynchronous AG environments:

  • More liberal use of REBUILD for critical indexes.         
  • Implement moderate delays (15-45 seconds).   
  • Monitor send queue and redo queue sizes closely.

General IOPS reduction techniques:

  • Leverage `@SortInTempdb = 'Y'` to spread I/O load.      
  • Use `@MaxDOP` to control parallelism (lower values reduce I/O).
  • Implement `@Delay` parameters appropriate to your environment.   
  • Use `@TimeLimit` to prevent runaway operations.

Storage pressure mitigation:

  • Pre-allocate transaction log space before maintenance.
  • Increase log backup frequency during maintenance (every 5-15 minutes).    
  • Use Resource Governor to limit I/O impact.    
  • Implement batched approaches with appropriate pauses.

Comprehensive maintenance approach:

  • Different strategies for different index sizes.    
  • Business-hour vs. off-hour configurations.  
  • Prioritization based on business impact.
  • Regular verification of fragmentation levels post-maintenance.

By implementing these guidelines and adapting the provided scripts to your specific environment, you can maintain optimal SQL Server index performance while minimizing production impact, even in complex Availability Group configurations.

optimization sql

Opinions expressed by DZone contributors are their own.

Related

  • Optimizing SQL Server Performance With AI: Automating Query Optimization and Predictive Maintenance
  • Essential Techniques for Performance Tuning in Snowflake
  • Essential Relational Database Structures and SQL Tuning Techniques
  • Common Mistakes to Avoid When Writing SQL Code

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: