Like other relational database systems, SQL Server uses Indexes in order to access data more quickly. There are several types of indexes available within SQL Server (clustered, non-clustered, XML and Full Text) but we will be focusing on non-clustered indexes as these, along with clustered indexes, are the most common. The primary difference between a clustered index and an non-clustered index is that a clustered index actually reorders the physical structure of the table and therefore you can only have one clustered index per table. On the flip-side, a non-clustered index has a structure separate form the data rows so you can have multiple non-clustered indexes on a single table.
With that in mind, the defining purpose of an index is to make data more easily and quickly accessible when queried. However, having too many indexes or, more specifically, having unused indexes can result in performance and data storage issues as SQL Server still has to maintain that index, even if it’s not being used.
The steps outlined below will aide you in the process of identifying non-clustered indexes that are not longer being used but are still being maintained by SQL Server.
It’s important to note that simply following the steps below does not guarantee that any single index should be removed. You will want to perform any DROP INDEX commands in a lab environment prior to making any changes in your production environment. I also recommend scripting out any indexes that you intend to drop as a quick means of adding it back should you notice any performance issues in your SQL Server environment after removing the index.
The following script will provide you with the relevant information related to the indexes currently residing in your SQL Server environment. Though there is quite a bit going on here the majority of the information is being pulled from the sys.dm_db_index_usage_stats Dynamic Management View (DMV).
This DMV will return data regarding how many times the index was utilized for user queries.
SELECT o.name AS ObjectName, i.name AS IndexName, o.create_date AS CreationDate, i.index_id AS IndexID, u.user_seeks AS UserSeeks, u.user_scans AS UserScans, u.user_lookups AS UserLookups, u.user_updates AS UserUpdates, p.TableRows FROM sys.dm_db_index_usage_stats u INNER JOIN sys.indexes i ON i.index_id = u.index_id AND u.OBJECT_ID = i.OBJECT_ID INNER JOIN sys.objects o ON u.OBJECT_ID = o.OBJECT_ID INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.OBJECT_ID FROM sys.partitions p GROUP BY p.index_id, p.OBJECT_ID) p ON p.index_id = u.index_id AND u.OBJECT_ID = p.OBJECT_ID WHERE OBJECTPROPERTY(u.OBJECT_ID,'IsUserTable') = 1 AND u.database_id = DB_ID() AND i.type_desc ='nonclustered' AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND o.is_ms_shipped <> 1 ORDER BY (u.user_seeks + u.user_scans + u.user_lookups)There is also data being pulled from other DMVs in order to provide additional information regarding the index such as the IndexID , CreationDate and number of table rows for the table where the index resides. This information can be useful when deciding what action to take on a particular index.
The following screenshot illustrates the output of the above script.
Image may be NSFW.
Clik here to view.

The main columns that you want to review pertain to the UserSeeks , UserScans , UserLookups and UserUpdates .
User Seeksindicates how many times an index seek occurred for a particular index. User Seeks are the ideal way to access data as they are the fastest as less resource intensive option. You generally want to see a higher value of Seeks than Scans or Lookups.
A User Scan occurs when multiple rows of data are searched in order to locate the data being pulled back by the query. This is less ideal than a user seek and you want to try to avoid these if possible.
User Lookupsindicate the number of times a query had to pull the data from a clustered index or from the heap.
User Updatesindicate the number of times the index was updated due to changes within the data.
In order to identify indexes that are not being used, simply refer to the above columns. If you see records where no UserSeeks , UserScans or UserLookups are occurring but UserUpdates are, then this means that SQL Server has not used the index but is still maintaining the index.
The previous screenshot shows a handful of indexes that are prime candidates for being dropped.
Though this is not an step by step guide for when to drop an index, it should provide you with some useful information regarding indexes that are not being used. It is up to you to decide if any particular index should be dropped from your environment.
Additionally, it is important to remember that the data provided by DMVs is wiped out when the SQL Server service is restarted. Make sure your SQL Server has been up and running for a reasonable period of time before analyzing this data and making any decisions regarding whether to keep or drop a particular index.