Possible Bad Non-Clustered Indexes
Identifying Unused Indexes in Your SQL Database
Are you looking to optimize your SQL database performance? One common area for improvement is identifying and addressing **unused indexes**. Indexes are crucial for speeding up data retrieval, but having too many, or indexes that aren't being utilized, can actually hinder performance by increasing write overhead.
Here's a handy SQL query that can help you pinpoint those underperforming or completely unused non-clustered indexes in your SQL Server database. This query focuses on indexes that have significantly more writes (updates) than reads (seeks, scans, lookups), suggesting they might not be pulling their weight.
SELECT
SCHEMA_NAME(o.[schema_id]) AS [Schema Name],
OBJECT_NAME(s.[object_id]) AS [Table Name],
i.name AS [Index Name],
i.index_id,
i.is_disabled,
i.is_hypothetical,
i.has_filter,
i.fill_factor,
s.user_updates AS [Total Writes],
s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads],
s.user_updates - (s.user_seeks + s.user_scans + s.user_lookups) AS [Difference]
FROM
sys.dm_db_index_usage_stats AS s WITH (NOLOCK)
INNER JOIN
sys.indexes AS i WITH (NOLOCK) ON s.[object_id] = i.[object_id] AND i.index_id = s.index_id
INNER JOIN
sys.objects AS o WITH (NOLOCK) ON i.[object_id] = o.[object_id]
WHERE
OBJECTPROPERTY(s.[object_id], 'IsUserTable') = 1
AND s.database_id = DB_ID()
AND s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups)
AND i.index_id > 1
AND i.[type_desc] = N'NONCLUSTERED'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
AND i.is_unique = 0
ORDER BY
[Difference] DESC,
[Total Writes] DESC,
[Total Reads] ASC
OPTION (RECOMPILE);
Understanding the Query:
- This query joins **`sys.dm_db_index_usage_stats`** (which tracks index usage), **`sys.indexes`** (for index metadata), and **`sys.objects`** (for table names).
- We filter for **user tables** (`OBJECTPROPERTY(s.[object_id], 'IsUserTable') = 1`) within the **current database** (`s.database_id = DB_ID()`).
- The core of the logic is `s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups)`. This condition looks for indexes where the number of writes significantly exceeds the number of reads, indicating a potential candidate for removal or modification.
- We specifically target **non-clustered indexes** (`i.[type_desc] = N'NONCLUSTERED'`) and exclude primary keys, unique constraints, and unique indexes, as these often serve other purposes beyond just speeding up reads.
- The results are ordered to show indexes with the largest difference between writes and reads first, followed by those with higher total writes.
Running this query can give you valuable insights into your database's indexing strategy. Before making any changes, always thoroughly analyze the results and consider the specific needs of your application. Removing an index without proper evaluation could lead to performance regressions in other areas.