Skip to content

Index Review Scripts

-- Index Usage Statistics Analysis
-- This query provides comprehensive index usage patterns and performance metrics

SELECT 
    OBJECT_SCHEMA_NAME(ius.object_id) AS schema_name,
    OBJECT_NAME(ius.object_id) AS table_name,
    i.name AS index_name,
    i.type_desc AS index_type,
    i.is_unique,
    i.is_primary_key,
    
    -- Usage statistics
    ius.user_seeks,
    ius.user_scans,
    ius.user_lookups,
    ius.user_updates,
    
    -- Total reads (seeks + scans + lookups)
    (ius.user_seeks + ius.user_scans + ius.user_lookups) AS total_reads,
    
    -- Read vs Write ratio
    CASE 
        WHEN ius.user_updates = 0 THEN 'Read Only'
        WHEN (ius.user_seeks + ius.user_scans + ius.user_lookups) = 0 THEN 'Write Only'
        ELSE CAST(ROUND(
            (ius.user_seeks + ius.user_scans + ius.user_lookups) * 1.0 / ius.user_updates, 2
        ) AS VARCHAR(10))
    END AS read_write_ratio,
    
    -- Last usage timestamps
    ius.last_user_seek,
    ius.last_user_scan,
    ius.last_user_lookup,
    ius.last_user_update,
    
    -- Index size information
    ps.used_page_count,
    ps.reserved_page_count,
    CAST(ps.used_page_count * 8.0 / 1024 AS DECIMAL(10,2)) AS size_mb,
    
    -- Fragmentation info (optional - can be resource intensive)
    -- ps.avg_fragmentation_in_percent,
    
    -- Usage classification
    CASE 
        WHEN (ius.user_seeks + ius.user_scans + ius.user_lookups) = 0 THEN 'UNUSED'
        WHEN ius.user_seeks > (ius.user_scans + ius.user_lookups) THEN 'SEEK_HEAVY'
        WHEN ius.user_scans > (ius.user_seeks + ius.user_lookups) THEN 'SCAN_HEAVY'
        WHEN ius.user_lookups > (ius.user_seeks + ius.user_scans) THEN 'LOOKUP_HEAVY'
        ELSE 'MIXED'
    END AS usage_pattern

FROM sys.dm_db_index_usage_stats ius
INNER JOIN sys.indexes i ON ius.object_id = i.object_id AND ius.index_id = i.index_id
INNER JOIN sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ps 
    ON ius.object_id = ps.object_id AND ius.index_id = ps.index_id
WHERE ius.database_id = DB_ID()
    AND OBJECT_NAME(ius.object_id) IS NOT NULL
    AND i.type_desc != 'HEAP'  -- Exclude heaps, focus on indexes

-- Order by total usage (most active indexes first)
ORDER BY (ius.user_seeks + ius.user_scans + ius.user_lookups + ius.user_updates) DESC;

-- Additional query to find potentially unused indexes
-- (indexes with no reads and minimal writes)
/*
SELECT 
    OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,
    OBJECT_NAME(i.object_id) AS table_name,
    i.name AS index_name,
    i.type_desc AS index_type,
    COALESCE(ius.user_seeks, 0) AS user_seeks,
    COALESCE(ius.user_scans, 0) AS user_scans,
    COALESCE(ius.user_lookups, 0) AS user_lookups,
    COALESCE(ius.user_updates, 0) AS user_updates,
    CAST(ps.used_page_count * 8.0 / 1024 AS DECIMAL(10,2)) AS size_mb
FROM sys.indexes i
LEFT JOIN sys.dm_db_index_usage_stats ius 
    ON i.object_id = ius.object_id 
    AND i.index_id = ius.index_id 
    AND ius.database_id = DB_ID()
LEFT JOIN sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ps 
    ON i.object_id = ps.object_id AND i.index_id = ps.index_id
WHERE i.type_desc != 'HEAP'
    AND i.is_primary_key = 0
    AND i.is_unique_constraint = 0
    AND OBJECT_NAME(i.object_id) IS NOT NULL
    AND (ius.user_seeks IS NULL OR ius.user_seeks = 0)
    AND (ius.user_scans IS NULL OR ius.user_scans = 0)
    AND (ius.user_lookups IS NULL OR ius.user_lookups = 0)
ORDER BY size_mb DESC;
*/