SQL Server  

Detecting Unused or Redundant Indexes Automatically in SQL Server

Indexes are essential for query performance in SQL Server. They allow the database engine to locate data efficiently without scanning entire tables. However, over time, unused or redundant indexes can accumulate, leading to:

  • Wasted disk space

  • Increased maintenance overhead (e.g., during inserts, updates, and deletes)

  • Slower overall performance due to unnecessary index updates

Detecting and removing unused or redundant indexes is crucial for database optimization. This article provides a comprehensive guide on automatically identifying unnecessary indexes, with practical examples for SQL Server and integration with ASP.NET Core applications.

Table of Contents

  1. Understanding Index Usage

  2. Why Unused or Redundant Indexes Hurt Performance

  3. How SQL Server Tracks Index Usage

  4. Identifying Unused Indexes Using DMVs

  5. Detecting Redundant Indexes

  6. Automating Index Analysis

  7. Best Practices for Index Management

  8. Integrating Index Analysis in ASP.NET Core

  9. Monitoring and Reporting

  10. Conclusion

1. Understanding Index Usage

Indexes improve query performance by enabling SQL Server to quickly locate rows. Types of indexes include:

  • Clustered indexes – determine physical order of data

  • Non-clustered indexes – separate structure referencing table rows

  • Unique indexes – enforce uniqueness

While necessary indexes improve read performance, unused or overlapping indexes are detrimental.

2. Why Unused or Redundant Indexes Hurt Performance

  1. Disk Space Consumption – extra indexes consume storage unnecessarily.

  2. DML Overhead – INSERT, UPDATE, DELETE operations must maintain indexes.

  3. Maintenance Costs – rebuilding or reorganizing indexes increases workload.

  4. Query Performance Confusion – redundant indexes may confuse the query optimizer.

Removing unnecessary indexes can improve overall database performance and reduce storage costs.

3. How SQL Server Tracks Index Usage

SQL Server provides Dynamic Management Views (DMVs) to monitor index usage:

  • sys.dm_db_index_usage_stats – tracks seek, scan, lookup, and update counts

  • sys.indexes – contains index definitions

  • sys.tables – contains table information

These DMVs can be combined to identify indexes that exist but are rarely used.

4. Identifying Unused Indexes Using DMVs

4.1 Query to Detect Unused Indexes

SELECT 
    OBJECT_NAME(i.object_id) AS TableName,
    i.name AS IndexName,
    i.index_id,
    user_seeks, user_scans, user_lookups, user_updates
FROM sys.indexes i
JOIN sys.dm_db_index_usage_stats s
    ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
AND i.is_primary_key = 0
AND i.is_unique = 0
AND user_seeks = 0
AND user_scans = 0
AND user_lookups = 0
ORDER BY OBJECT_NAME(i.object_id);

Explanation

  • Filters only user tables, not system tables

  • Excludes primary key and unique indexes

  • Selects indexes never used for reads (seeks, scans, lookups)

  • Helps identify candidate indexes for removal

4.2 Including Last Usage Time

SELECT 
    OBJECT_NAME(i.object_id) AS TableName,
    i.name AS IndexName,
    i.index_id,
    s.last_user_seek,
    s.last_user_scan,
    s.last_user_lookup,
    s.user_updates
FROM sys.indexes i
LEFT JOIN sys.dm_db_index_usage_stats s
    ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
AND i.is_primary_key = 0
AND i.is_unique = 0
ORDER BY s.last_user_seek;
  • Shows last time index was used

  • Helps detect indexes rarely accessed

5. Detecting Redundant Indexes

Redundant indexes occur when multiple indexes cover the same columns, leading to duplication.

5.1 Query to Find Redundant Indexes

SELECT 
    OBJECT_NAME(i1.object_id) AS TableName,
    i1.name AS IndexName1,
    i2.name AS IndexName2,
    i1.index_id AS IndexId1,
    i2.index_id AS IndexId2
FROM sys.indexes i1
JOIN sys.indexes i2
    ON i1.object_id = i2.object_id
WHERE i1.index_id < i2.index_id
AND i1.key_index_id = i2.key_index_id
AND i1.is_primary_key = 0
AND i2.is_primary_key = 0;
  • Identifies indexes on the same table with similar key columns

  • Can be enhanced using sys.index_columns for column-level comparison

6. Automating Index Analysis

You can create a scheduled SQL job or stored procedure to regularly detect unused/redundant indexes.

Example: Stored Procedure to Detect Unused Indexes

CREATE PROCEDURE DetectUnusedIndexes
AS
BEGIN
    SELECT 
        OBJECT_NAME(i.object_id) AS TableName,
        i.name AS IndexName,
        s.user_seeks, s.user_scans, s.user_lookups, s.user_updates
    FROM sys.indexes i
    LEFT JOIN sys.dm_db_index_usage_stats s
        ON i.object_id = s.object_id AND i.index_id = s.index_id
    WHERE OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
    AND i.is_primary_key = 0
    AND i.is_unique = 0
    AND (s.user_seeks IS NULL OR (s.user_seeks = 0 AND s.user_scans = 0 AND s.user_lookups = 0))
END
  • Can be scheduled weekly or monthly

  • Reports candidate indexes for review before dropping

7. Best Practices for Index Management

  1. Review Usage Before Dropping: never drop an index without confirming it’s not used.

  2. Monitor Updates: some indexes may not be read often but are critical for foreign key or unique constraints.

  3. Test in Development: validate performance impact before removing in production.

  4. Maintain Documentation: track indexes and their purpose for long-term maintenance.

  5. Combine with Index Rebuilds: optimize maintenance by removing unused indexes and reorganizing others.

8. Integrating Index Analysis in ASP.NET Core

You can integrate automatic index analysis into ASP.NET Core applications:

8.1 Using Dapper or EF Core

var unusedIndexes = await _dbContext.UnusedIndexes
    .FromSqlRaw("EXEC DetectUnusedIndexes")
    .ToListAsync();

foreach(var index in unusedIndexes)
{
    Console.WriteLine($"{index.TableName} - {index.IndexName}");
}

8.2 Scheduled Background Task

  • Use Hosted Services in ASP.NET Core to run weekly index analysis

  • Generate reports or alerts if unused/redundant indexes are found

public class IndexAnalysisService : BackgroundService
{
    private readonly ILogger<IndexAnalysisService> _logger;
    private readonly AppDbContext _dbContext;

    public IndexAnalysisService(ILogger<IndexAnalysisService> logger, AppDbContext dbContext)
    {
        _logger = logger;
        _dbContext = dbContext;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var unusedIndexes = await _dbContext.UnusedIndexes.FromSqlRaw("EXEC DetectUnusedIndexes").ToListAsync();
            _logger.LogInformation("Found {Count} unused indexes", unusedIndexes.Count);

            await Task.Delay(TimeSpan.FromDays(7), stoppingToken);
        }
    }
}

9. Monitoring and Reporting

  • Use SQL Server Reporting Services (SSRS) or Power BI to visualize unused indexes

  • Generate weekly/monthly reports for database administrators

  • Combine with Query Store metrics to track index usage trends

Conclusion

Unused or redundant indexes can significantly affect performance, storage, and maintenance costs. By automatically detecting these indexes:

  • Improve query performance

  • Reduce storage overhead

  • Simplify index maintenance

Key takeaways:

  • Use DMVs like sys.dm_db_index_usage_stats for index usage analysis

  • Detect redundant indexes using sys.index_columns

  • Automate detection with stored procedures and background services

  • Integrate analysis in ASP.NET Core applications for reporting and monitoring

By maintaining a clean and efficient set of indexes, you ensure that your SQL Server databases remain fast, reliable, and maintainable.