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.
Join the DZone community and get the full member experience.
Join For FreeIndex 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
Parameter | Description | Impact |
---|---|---|
`@Databases` | Target databases | Controls scope of operation |
`@FragmentationLow` | Action for low fragmentation | Typically NULL (no action) |
`@FragmentationMedium` | Action for medium fragmentation | Usually REORGANIZE |
`@FragmentationHigh` | Action for high fragmentation | REBUILD or REORGANIZE |
`@FragmentationLevel1` | Low/medium threshold (%) | Typically 5-15% |
`@FragmentationLevel2` | Medium/high threshold (%) | Typically 30-40% |
`@PageCountLevel` | Minimum index size to process | Excludes small indexes |
`@SortInTempdb` | Use tempdb for sorting | Reduces production database I/O |
`@MaxDOP` | Degree of parallelism | Controls CPU utilization |
`@FillFactor` | Index fill factor | Controls free space in pages |
`@PadIndex` | Apply fill factor to non-leaf levels | Affects overall index size |
`@LOBCompaction` | Compact LOB data | Reduces storage for LOB columns |
`@UpdateStatistics` | Update statistics after rebuild | 'ALL', 'COLUMNS', 'INDEX', NULL |
`@OnlyModifiedStatistics` | Only update changed statistics | Reduces unnecessary updates |
`@TimeLimit` | Maximum execution time (seconds) | Prevents runaway jobs |
`@Delay` | Pause between operations (seconds) | Reduces continuous resource pressure |
`@Indexes` | Specific indexes to maintain | Allows targeted maintenance |
`@MinNumberOfPages` | Minimum size threshold | Alternative to PageCountLevel |
`@MaxNumberOfPages` | Maximum size threshold | Limits operation to smaller indexes |
`@LockTimeout` | Lock timeout (seconds) | Prevents blocking |
`@LogToTable` | Log operations to table | Enables tracking/troubleshooting |
Parameter | Description | Recommended Setting |
`@AvailabilityGroups` | Target specific AGs | Limit scope when needed |
`@AvailabilityGroupReplicas` | Target specific replicas | 'PRIMARY' to limit AG impact |
`@AvailabilityGroupDatabases` | Target specific databases | Focus on critical databases |
Availability Group-Specific Parameters
Parameter | Description | Recommended Setting |
---|---|---|
`@AvailabilityGroups` | Target specific AGs | Limit scope when needed |
`@AvailabilityGroupReplicas` | Target specific replicas | 'PRIMARY' to limit AG impact |
`@AvailabilityGroupDatabases` | Target specific databases | Focus 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.
Opinions expressed by DZone contributors are their own.
Comments